@Java简单循环依赖的解决

浅谈循环依赖

第一次接触 循环依赖 是在我上JAVA课的过程中,我们老师在模仿Spring时提出的问题,依照我个人的想法,简单的总结为:在 注入 的过程中,生成A的对象需要new 一个 B,而生成B的对象需要new 一个 A,如果不加以处理,那么久会一直等待着对方的实例化,如果再复杂一些,A需要B,B需要C,C需要A,形成的间接递归很让人头大;所以,就如何解决这个问题,采取模仿Spring的方法,我们展开了一系列讨论。

对Spring的模仿

依照Spring的模式,我们核心的分成这几个类:
BeanFactory、BeanDefinition、BeanMethodDefinition、OnReadyBeanMethodDefinition、parameterDependence;
注解类我们创建了三个做测试的注解:
Component、Bean、Autowired;
接下来主要介绍这几个类的作用以及相互之间的运作方式:
我们采取用包扫描的方法,通过给一个Java包的路径,识别出其下所有的类(类名称),然后再判断这些类中是否被我们给出的特殊注解类注解过,按照不同的注解分成不同的类别(注解类、注解方法、注解成员);区分过后再通过注入反射机制,执行所有的注解方法 ,接下来,从最进本的注解类开始记录。

三个注解类

注解类主要是我们为了区分方法类成员创建的,这里我们划分:Component属于的注解,Bean是方法的注解,Autowired是类成员的注解;因为我们只是简单的模仿,所以不做过多的测试,其内代码以及说明如下都比较简单(如果不给name(),换成其他的也可以,注解的用处不只如此,这里不详细说明):

// Component注解类
@Retention(RUNTIME)
@Target(TYPE)
public @interface Component {String name() default "";
}
// Bean注解类
@Retention(RUNTIME)
@Target(METHOD)
public @interface Bean {String name() default "";
}
// Autowired注解类
@Retention(RUNTIME)
@Target(FIELD)
public @interface Autowired {String name() default "";
}

BeanDefinition类

我们采取包扫描的方法实现对各个类、方法和成员的区分,当扫描出一个带有Component注解的类的时候,我们将其放入BeanDefinition中,它的成员有“自身类型”、“将来实例化后的对象”;

// BeanDefinition类
public class BeanDefinition {private Class<?> klass;private Object object;BeanDefinition() {}Class<?> getKlass() {return klass;}void setKlass(Class<?> klass) {this.klass = klass;}Object getObject() {return object;}void setObject(Object object) {this.object = object;}@Overridepublic String toString() {return klass.getSimpleName() + " : " + object;}}

BeanMethodDefinition类

它是扫描出的带有Bean注解的方法,它的实现很简单,每一个BeanMethodDefinition只需要表明这个 方法 是“哪一个”、“它的参数个数”(看它是否还需要等待执行)、“这个类的实例化对象”(执行invoke的必要)、以及方法“Method”本身。具体的实现代码以及说明如下如下:

// BeanMethodDefinition类
public class BeanMethodDefinition {private Class<?> klass;private Object object;private Method method;private int parameterCount;public BeanMethodDefinition() {}Class<?> getKlass() {return klass;}void setKlass(Class<?> klass) {this.klass = klass;}Object getObject() {return object;}void setObject(Object object) {this.object = object;}Method getMethod() {return method;}void setMethod(Method method) {this.method = method;Parameter[] parameters = method.getParameters();// 得到所有的参数过后,如果参数的个数为0,不作处理,直接返回if (parameters.length <= 0) {this.parameterCount = 0;return;}// 如果参数的个数大于0,那么我们用HashMap将其装起来// 键为方法的getType(),值为null// 这样一来就容易对parameterCount进行赋值(map.size()即可)Map<Class<?>, Object> paraTypeMap = new HashMap<Class<?>, Object>();for (Parameter parameter : parameters) {paraTypeMap.put(parameter.getType(), null);}parameterCount = paraTypeMap.size();}int getParameterCount() {return parameterCount;}int decrease() {return --this.parameterCount;}}

其中,全部给的是包权限的 Setters 和 Getters,主要原因就是不想让外部对MethodDefinition进行操作(内部实现);
另外,parameterCount的赋值不太好实现,所以我们给出了比较人性化的实现——map.size(),算是比较精妙的一环节。

OnReadyBeanMethodDefinition类

OnReadyBeanMethodDefinition(以下简称为OnReady)算是一个中转站一样的存在,它是所有可执行方法的集合,里面只有一个泛型为BeanMethodDefinition的List,凡是在List中的方法,全都是可执行方法;

// OnReadyBeanMethodDefinition类
public class OnReadyBeanMethodDefinition {private List<BeanMethodDefinition> beanMethodDefinitionList;public OnReadyBeanMethodDefinition() {this.beanMethodDefinitionList = new ArrayList<BeanMethodDefinition>();}void in(BeanMethodDefinition bmd) {beanMethodDefinitionList.add(bmd);}BeanMethodDefinition next() {return beanMethodDefinitionList.remove(0);}boolean hasNext() {return !beanMethodDefinitionList.isEmpty();}}

这里面,为了方便以后遍历表,我们给出了常见的next()、hasNext()方法,算是“见贤思齐”。

ParameterDependence类

这个类就如同其名字一般,意在参数独立;大体的思路是将类型方法作为键值对,放入一个Map里面,如果方法可以执行,那么就直接放入OnReady表里去,本着这样的思路,本类中的主要方法分成了:addDependence()、matchDependence()、checkOnReady()三个,下来我们一步一步介绍;

parameterDependence里的成员:Map。

// ParameterDependence类
private static Map<Class<?>, List<BeanMethodDefinition>> parameterDependence;
static {parameterDependence = new HashMap<Class<?>, List<BeanMethodDefinition>>();
}

addDependence()方法
这个方法的作用就是把传过来的beanMethodDefinition检查一遍,然后将其加入Map中去

// ParameterDependence类
boolean addDependence(BeanMethodDefinition methodDefinition) {// 先检查传过来的methodDefinition是否需要参数才能执行// 如果不需要任何参数,那么就没有必要加入到Map里去// 直接返回false,后直接加入OnReady里就好Method method = methodDefinition.getMethod();int paraCount = method.getParameterCount();if (paraCount <= 0) {return false;}// 如果需要参数执行,先获得参数类型// 若Map(parameterDependence)存在该类型,那么直接加入List里,等待匹配// 若Map(parameterDependence)不存在该类型,那么创建一个List,加入到Map(parameterDependence)中去Parameter[] parameters = method.getParameters();for (Parameter parameter : parameters) {Class<?> type = parameter.getType();if (!parameterDependence.containsKey(type)) {parameterDependence.put(type, new ArrayList<BeanMethodDefinition>());}List<BeanMethodDefinition> bmdList = parameterDependence.get(type);bmdList.add(methodDefinition);}return true;
}

matchDependence()方法

此方法就是为了检查方法是否可以执行,它需要两个参数——对应方法的类型,以及OnReady列表

// ParameterDependence类
void matchDependence(Class<?> klass, OnReadyBeanMethodDefinition onReady) {// 根据传过来的Class检查Map(parameterDependence)里是否存在对应的ListList<BeanMethodDefinition> bmdList = parameterDependence.get(klass);// 不存在相对应列表,说明Map(parameterDependence)没有存入此Class,也就说明不是需要的类型// 直接返回if (bmdList == null) {return;}// 如果找到了对应列表,对表进行遍历,以此判断是否可以加入到OnReady里去for (BeanMethodDefinition bmd : bmdList) {// 因为参数已经能满足一个,所以此处用的是decrease();int paraCount = bmd.decrease();if (paraCount <= 0) {onReady.in(bmd);}bmdList.remove(bmd);if (bmdList.isEmpty()) {parameterDependence.remove(klass);}}
}

在matchDependence()方法中,有人或许对bmdList.remove(bmd);有疑问,“为什么要删除?你怎么知道该方法已经能执行了?这个参数满足了别的参数不一定能满足啊!不是应该在onReady.in(bmd);之后删吗?为什么要在外边删?”,当时在这个地方,我们也讨论了很久,下面简单的用一张表来解释:

Type( Class<?> ) List < BeanMethodDefinition >
ClassOne MethodOne,MethodTwo;
ClassTwo MethodOne;

我们假设我们扫描到了ClassOne,那么MethodTwo已经满足了参数要求可以执行了,MethodOne不满足参数要求,但如果我们不删除ClassOne里的MethodOne,那么它将一直不能满足要求,一直等待,显然不是我们所期待的那样,因此应该删掉。

checkOnReady()方法

此方法简单的用for循环遍历整个beanFactory,查找是否有符合参数条件可以执行的方法,用matchDependence方法加入到OnReady队列里去。

// ParameterDependence类
void checkOnReady(OnReadyBeanMethodDefinition onReady) {BeanFactory beanFactory = new BeanFactory();for (Class<?> klass : parameterDependence.keySet()) {if (beanFactory.getBeanDefinition(klass) != null)  {matchDependence(klass, onReady);}}
}

除了以上的几个主要方法外,还有个方法需要提及:
判断Map(parameterDependence)是否空的isEmpty()方法

 // ParameterDependence类boolean isEmpty() {return parameterDependence.isEmpty();}

最后,在介绍核心中的核心——BeanFactory类 之前,我们先写一个Exception类给自己,防止出现异常情况

HasNoBeanException类

这个类是一个简单的 异常处理类,与普通的异常类相同,它的实现很简单,它的给出主要是为了以后在给出循环依赖报错的时候方便给出提示(描述循环依赖),并且为了停止程序的运行,我们不抛出异常,也不用try catch{},而是自己给异常处理类处理:

// HasNoBeanException类
public class HasNoBeanException extends RuntimeException {// 申请专有的错误序列号,每个人的电脑都不一样private static final long serialVersionUID = -8760349479646485327L;public HasNoBeanException() {}public HasNoBeanException(String message) {super(message);}public HasNoBeanException(Throwable cause) {super(cause);}public HasNoBeanException(String message, Throwable cause) {super(message, cause);}public HasNoBeanException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}}

因为这个异常出现过后,之后的程序我们想让它不执行,直接停止,所以我们这里extends RunTimeException;

BeanFactory类

和Spring相同,BeanFactory是工厂模式的一个实现,它将应用配置和依赖说明从实际的应用代码以及说明如下中分离出来;
它的实现说起来麻烦实现起来更麻烦,可以说是核心中的核心,前面的一系列类,都是为了给它做铺垫;

首先我们先来看看需要的成员:

// BeanFactory类
private static final Map<String, BeanDefinition> beanPool;
static {beanPool = new HashMap<String, BeanDefinition>();
}

beanPool这个成员是一个比较基本的操作;它的作用就是将扫描到的类采用键值对的方法存起来,是算是 “工厂” 中的 “原料”,我们只能先把所有扫描到的 特殊的类(带有特殊注解的类),生成BeanDefinition存进beanPool里之后才能进行其他操作;
而firstGetBean是为了判断是否是第一次注入(后会在getBean方法中给出具体说明);

我们用的是注解的方式区分所有的类,因此,我们可以说BeanFactory的用处就是采用包扫描的方法把所有带有特殊注解的类与普通类区分开,扫描到带有特殊注解的类就生成一个相对应的BeanDefinition 放进 所谓的池子(beanPool)里,然后收集所有的带有Bean注解的方法,当每次扫描完了之后,我们可以检查是否有存在满足参数关系的方法(调用checkOnReady方法),然后 注入执行 所有满足参数条件的方法。

 // BeanFactory类 public void scanBeanByPackage(String packageName) {OnReadyBeanMethodDefinition orbmd = new OnReadyBeanMethodDefinition();parameterDependence parameterDependence = new parameterDependence();new PackageScanner() {@Overridepublic void dealClass(Class<?> klass) {// 排除八大基本类型,数组,枚举类型,接口类型,以及没有Compoent注解的类型if (klass.isPrimitive()|| klass.isAnnotation()|| klass.isArray()|| klass.isEnum()|| klass.isInterface()|| !klass.isAnnotationPresent(Component.class)) {return;}// 用扫描到的类名称,创建一个新的BeanDefinition放进beanPool里Object object = null;try {object = klass.newInstance();BeanDefinition bd = new BeanDefinition();bd.setKlass(klass);bd.setObject(object);beanPool.put(klass.getName(), bd);} catch (Exception e) {e.printStackTrace();}// 查找所有的Bean方法// 只能先收集起来collectBeanMethod(klass, object, orbmd);}}.packageScanner(packageName);// 每次扫描完后检查OnReady列表中是否存在可以执行的方法// 可以执行的方法,直接执行即可parameterDependence.checkOnReady(orbmd);processBeanMethod(parameterDependence, orbmd);}

在我们得到所有的带有Component注解类之后,就开始着手找带有Bean注解的方法了,collectBeanMethod()方法的具体实现如下:

// BeanFactory类 // 收集所有的带有Bean注解的方法
private static void collectBeanMethod(Class<?> klass, Object object, OnReadyBeanMethodDefinition orbmd) {// 新生成一个parameterDependence对象// 但实质上用的是同一个空间(static)parameterDependence pd = new parameterDependence();// 得到所有的方法Method[] methods = klass.getDeclaredMethods();for (Method method : methods) {// 没有Bean注解的方法,直接不处理if (!method.isAnnotationPresent(Bean.class)) {continue;}// 将带有Bean注解的方法生成对应的BeanMethodDefinition放入parameterDenpendance中BeanMethodDefinition bmd = new BeanMethodDefinition();bmd.setKlass(klass);bmd.setMethod(method);bmd.setObject(object);// 如果是不需要参数的方法(意味着直接可以执行),那么直接放入OnReady中if (pd.addDependence(bmd) == false) {orbmd.in(bmd);}}
}

收集过后我们用parameterDependence.checkOnReady(orbmd);遍历一遍表,检查是否有满足参数关系的方法,然后就该执行了;
执行之前,我们考虑:采取反射机制执行该方法时,我们要得到所有的参数值,还需要OnReady列表中的BeanMethodDefinition中的实例化对象Object,执行完过后,若该方法还有返回值,我们还需要检查此返回值类型是不是parameterDependence中被需要的类型,下面给出具体的实现代码以及说明如下:

 // BeanFactory类 private void processBeanMethod(ParameterDependence parameterDependence, OnReadyBeanMethodDefinition orbmd) {// 当OnReady列表中还有BeanMethodDefinition的时候// 说明还有可执行的方法while (orbmd.hasNext()) {BeanMethodDefinition bmd = orbmd.next();Object object = bmd.getObject();Method method = bmd.getMethod();// 由getParameterValue得到所有的参数值Object[] parameterValues = getParameterValue(method);try {// 用反射机制执行该方法Object result = method.invoke(object, parameterValues);// 将执行过后生成的对应类生成beanDefinition放入beanPool里去// 这是防止万一生成的 对象的类型 是别的方法 所需要的 参数类型Class<?> resultClass = result.getClass();BeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setInject(true);beanDefinition.setKlass(resultClass);beanDefinition.setObject(result);beanPool.put(resultClass.getName(), beanDefinition);// 用matchDependance判断是否需要该类型parameterDependence.matchDependance(resultClass, orbmd);                } catch (Exception e) {e.printStackTrace();}}}

其中由方法类型得到所有的参数值的getParameterValue()方法如下:

 // BeanFactory类 private Object[] getParameterValue(Method method) {Parameter[] parameters = method.getParameters();int paraCount = parameters.length;// 如果不需要参数,直接返回空数组if (paraCount <= 0) {return new Object[] {};}Object[] parameterValues = new Object[paraCount];int index = 0;// 从beanPool中得到for (Parameter parameter : parameters) {Class<?> type = parameter.getType();parameterValues[index++] = getBeanDefinition(type).getObject();}return parameterValues;}

解决完这些问题过后,我们就开始考虑 注入 的过程了,注入的时候,我们从beanPool池子中得到beanDefinition,如果不是第一次注入,还要检查是否存在 循环依赖
为了简便我们的操作,更清晰的发现并防止循环依赖关系,我们准备了两个方法来解决问题:getBean()、injectProperties();
getBean()方法:用来得到有意义的实例化对象,且我们允许用两种方式得到对应的实例化对象;
injectProperties():给beanDefinition中的对象(Object object)注入真正的值;
为了解决循环依赖,我们给BeanDefinition类中新增了成员,并更改了构造方法:

 // 更改过后的BeanDefinition类 // BeanDefinition中新增成员及方法private boolean inject;BeanDefinition() {this.inject = false;}boolean isInject() {return inject;}void setInject(boolean inject) {this.inject = inject;}

此时,回归到基本的问题中:A需要B,B需要A,以此构成了依赖关系,但是,如果我们在一开始用inject区分哪些已经注入过了,在每一次的注入之前我们先检查inject的值,如果已经注入过,直接返回或停止循环,我们就可以防止继续进一步的调用,以免构成间接递归;

以下是具体代码以及说明如下以及说明如下:

getBean()方法:

 // BeanFactory类 // 从对应的类名称中得到类对应的对象public <T> T getBean(String className) throws RuntimeException {// 由beanPool中得到beanDefinition// 如果没有得到,返回nullBeanDefinition bd = getBeanDefinition(className);if (bd == null) {return null;}// 得到相对应的beanDefinition后,直接开始注入过程Object result = bd.getObject();if (!bd.isInject()) {bd.setInject(true);injectProperties(bd);}return (T) result;}
 // BeanFactory类 // 实质上还是调用了上一个getBean()方法public <T> T getBean(Class<?> klass) throws RuntimeException {return  getBean(klass.getName());}

injectProperties()方法:

 // BeanFactory类 private void injectProperties(BeanDefinition beanDefinition)  {// 开始注入Class<?> klass = beanDefinition.getKlass();Object object = beanDefinition.getObject();// 得到所有的成员Field[] fields = klass.getDeclaredFields();for (Field field : fields) {// 没有Autowired注解,直接打断if (!field.isAnnotationPresent(Autowired.class)) {continue;}// 设置成员可访问field.setAccessible(true);Object value = getBean(field.getType());// 如果发现从getBean没有得到相关的实例化对象,直接抛异常。if (value == null) {throw new HasNoBeanException("类[" + klass.getName()+ "]的[" + field.getName()+ "]成员没有对应的Bean!");}try {// 注入对应的成员以及值field.set(object, value);} catch (IllegalAccessException e) {e.printStackTrace();}}// 设置isInject为true,为防止循环依赖beanDefinition.setInject(true);}

但这样是显然是不够的,Spring还可以发现基本的依赖关系给出提示啊,我们就做不到吗?答案是否定的。
我们创建ParameterDependence类的初衷就是为了解决依赖关系!

因此,在ParameterDependence类中再给出一个方法,并在getBean()方法执行的时候调用。

用来 说明依赖关系getDependenceMessage()方法(由我们以前写好的异常处理类相结合给出警告提示):

 // ParameterDependence类新增方法String getDependenceMessage() {StringBuffer res = new StringBuffer();for (Class<?> klass : parameterDependence.keySet()) {List<BeanMethodDefinition> bmdList = parameterDependence.get(klass);for (BeanMethodDefinition bmd : bmdList) {res.append('\n').append(bmd.getMethod()).append("缺少对应参数").append(klass.getName());}res.append('\n');}return res.toString();}

新改进过后的getBean()方法:
此外,还在本类中新增了成员配合getBean()使用:

// BeanFactory类// BeanFactory新增成员
private static boolean firstGetBean = true;
 // BeanFactory类// 更改getBean()方法public <T> T getBean(String className) throws RuntimeException {if (firstGetBean) {// 如果是第一次getBean,那么需要初始化parameterDependence// 并且如果parameterDependence不为空,证明有未处理的依赖关系// 直接抛出异常ParameterDependence parameterDependence = new ParameterDependence();if (!parameterDependence.isEmpty()) {throw new RuntimeException(parameterDependence.getDependanceMessage());}}// 由beanPool中得到beanDefinition// 如果没有得到,返回nullBeanDefinition bd = getBeanDefinition(className);if (bd == null) {return null;}// 得到相对应的beanDefinition后,直接开始注入过程Object result = bd.getObject();if (!bd.isInject()) {bd.setInject(true);injectProperties(bd);}return (T) result;}

终于,经过一系列的改进,我们可以给出几个类来测试测试了。
测试类1:

// 测试类1
public class ClassTwo {private ClassThree three;public ClassTwo(ClassThree three) {this.three = three;}public ClassThree getThree() {return three;}public void setThree(ClassThree three) {this.three = three;}@Overridepublic String toString() {return "ClassTwo [three=" + three + "]";}}

测试类2:

// 测试类2
public class ClassThree {private ClassTwo two;public ClassThree(ClassTwo two) {this.two = two;}public ClassTwo gettwo() {return two;}public void settwo(ClassTwo two) {this.two = two;}@Overridepublic String toString() {return "ClassThree [two=" + two + "]";}}

两个类创建好之后你可能会发现,这两个类并没有写注解啊,这怎么可能被扫描到呢?不要急,真正的类在这里:

@Component
public class Configuration {public Configuration() {}@Beanpublic ClassThree getClassThree(ClassTwo two) {return new ClassThree(two);}@Beanpublic ClassTwo getClassThree(ClassThree three) {return new ClassTwo(three);}}

因为我们写的是带有Bean注解方法的注入,所以自然要在方法中进行测试,从这个类扫描到后会生成BeanDefinition类的对象,而这个类的Bean方法会生成BeanMethodDefinition类的对象。
好了,接下来就是测试主函数Demo类了:

public class Demo {public static void main(String[] args) {BeanFactory beanFactory = new BeanFactory();beanFactory.scanBeanByPackage("com.mec.k.spring_imitate.some_classes");beanFactory.getBean("");}
}

执行结果:

Exception in thread "main" java.lang.RuntimeException: public com.mec.k.spring_imitate.some_classes.ClassThree com.mec.k.spring_imitate.some_classes.Configuration.getClassThree(com.mec.k.spring_imitate.some_classes.ClassTwo)缺少对应参数com.mec.k.spring_imitate.some_classes.ClassTwopublic com.mec.k.spring_imitate.some_classes.ClassTwo com.mec.k.spring_imitate.some_classes.Configuration.getClassThree(com.mec.k.spring_imitate.some_classes.ClassThree)缺少对应参数com.mec.k.spring_imitate.some_classes.ClassThreeat com.mec.k.spring_imitate.core.BeanFactory.getBean(BeanFactory.java:203)
at com.mec.k.spring_imitate.test.Demo.main(Demo.java:9)

可以清晰的看出,描述出来的依赖关系很清晰。

最后的最后我是不是忘了一件事?哦,对了,包扫描,那就是另一方面的工具了,详情参见:https://blog.csdn.net/qq_43594241/article/details/102165697

Java简单循环依赖的解决 —— spring_imitate(Spring的模仿)相关推荐

  1. java 循环依赖_浅谈Spring解决循环依赖的三种方式

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种: ...

  2. Spring-bean的循环依赖以及解决方式___Spring源码初探--Bean的初始化-循环依赖的解决

    本文主要是分析Spring bean的循环依赖,以及Spring的解决方式. 通过这种解决方式,我们可以应用在我们实际开发项目中. 什么是循环依赖? 怎么检测循环依赖 Spring怎么解决循环依赖 S ...

  3. Spring中-IOC-Bean的初始化-循环依赖的解决

    前言 在实际工作中,经常由于设计不佳或者各种因素,导致类之间相互依赖.这些类可能单独使用时不会出问题,但是在使用Spring进行管理的时候可能就会抛出BeanCurrentlyInCreationEx ...

  4. map 循环_被问到Spring循环依赖怎么解决?秀给面试官看!内附图解

    不知道最近有没有被一道Java面试题刷爆朋友圈,Spring框架的循环依赖如何解决.我收到了不少粉丝的提问,在了解到之后,也去网上查询了一些资料,自己也询问了身边的同事,总结出以下几个方面,今天就和我 ...

  5. spring相互依赖怎么解决_被问到Spring循环依赖怎么解决?秀给面试官看!内附图解...

    不知道最近有没有被一道Java面试题刷爆朋友圈,Spring框架的循环依赖如何解决.我收到了不少粉丝的提问,在了解到之后,也去网上查询了一些资料,自己也询问了身边的同事,总结出以下几个方面,今天就和我 ...

  6. Java程序员必须掌握的Spring依赖管理原理

    Spring依赖注入 依赖注入(Dependency Injection)的意思就是对象通过构造器函数参数,工厂方法的参数,或者成员属性,定义了对象的依赖对象:容器在创建该对象时会负责注入这些依赖.这 ...

  7. 【Spring面试题】循环依赖如何解决?

    1.什么是循环依赖 循环依赖指的是两个类中的属性相互依赖对方:例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环. 2.循环依赖的两种情况及其解决方案 2.1 构造器的循环依赖 这 ...

  8. Spring循环依赖及其解决方式

    部分原文链接:java 循环依赖_Java详解之Spring Bean的循环依赖解决方案_以太创服的博客-CSDN博客 1,什么是循环依赖: 在spring中,对象的创建是交给Spring容器去执行的 ...

  9. Spring循环依赖以及解决方法

    什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A. Spring中循环依赖场景有: (1)构造器的循环依 ...

  10. java循环依赖问题怎么解决_Spring如何解决循环依赖的问题

    前言 在面试的时候这两年有一个非常高频的关于spring的问题,那就是spring是如何解决循环依赖的.这个问题听着就是轻描淡写的一句话,其实考察的内容还是非常多的,主要还是考察的应聘者有没有研究过s ...

最新文章

  1. Tips_方格拼图效果
  2. 我对图像金字塔的理解及OpenCV下的实现代码
  3. Python编程基础:第三十九节 面向对象编程Object Oriented Programming
  4. 将输出流转换成输入流
  5. OpenCV 基本绘制Basic Drawing
  6. GitHub for Windows使用教程(二) 分支的使用
  7. 什么样的企业适合做响应式网站
  8. 数据仓库ETL之DataX(一)简介
  9. Swift相关api功能介绍与使用
  10. json序列化时忽略属性设置
  11. C语言dos游戏编程,◣电脑游戏编程入门 (DOS)◥
  12. c++ 调用labview_Namisoft解析基于Labview的自动化精密阻抗分析系统
  13. 操作系统模拟进程调度实验报告java_操作系统实验2进程调度实验报告.doc
  14. centos7修改mysql默认端口号_修改mysql默认端口方法(linux centos 7)
  15. 基于无人机的目标检测平台——数据中转(安卓App)
  16. options.add_argument(r'--user-data-dir=C:\Users\name\AppData\Local\Google\Chrome\User Data') 绕过登录
  17. win7系统备份还原软件_比ghost快200%!备份还原系统真正首选的神器
  18. 计算机内置管理员,Win10无法使用内置管理员账户打开应用怎么解决?
  19. Unity3D相机限制移动范围
  20. Flask 和 Django 的比较和选择

热门文章

  1. 8700K + z370 安装黑苹果 Mojave
  2. 谷歌云服务器账号,免费使用谷歌云服务器一年
  3. 嵌入式开发入门基础篇
  4. Docker安装、开发环境配置及项目搭建(二,Docker桌面应用)
  5. 岁月温柔-3 清明节医院复查,去昆明过冬是否会是一种奢望?
  6. Pr:用Audition协作处理音频
  7. 大专计算机专业学期计划,大专三年学习目标计划
  8. word交叉引用标题序号 编号变化问题
  9. 江苏省等保测评机构项目测评收费价格标准参考
  10. x^2+y^2=2ax