前言:前文【spring源码分析】IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean对象的流程,这里接着分析createBeanInstance方法中的剩余流程。


直接看createBeanInstance中剩余的流程:

 // AbstractAutowireCapableBeanFactory
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
// 做同步
synchronized (mbd.constructorArgumentLock) {
// 如果已缓存的解析构造函数或者工厂方法不为null,则可以利用构造函数解析
// 因为需要根据参数确认到底使用哪个构造函数,该过程比较消耗性能,所以采用缓存机制
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
// 已经解析好了,直接注入即可
if (resolved) {
// autowire自动注入,调用构造函数自动注入
if (autowireNecessary) {
return autowireConstructor(beanName, mbd, null, null);
} else {
// 使用默认构造函数构造
return instantiateBean(beanName, mbd);
}
} // Need to determine the constructor...
// 确定解析的构造函数
// 主要是检查已经注册的SmartInstantiationAwareBeanPostProcessor
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 确定构造方法进行bean创建
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
} // 有参数,又没有获取到构造方法,则只能调用无参构造方法来创建实例
// 这是一个兜底的方法
// No special handling: simply use no-arg constructor.
return instantiateBean(beanName, mbd);

分析:

剩余流程还剩下两大分支:构造函数自动注入初始化bean和默认构造函数初始化bean。

#1 AbstractAutowireCapableBeanFactory#autowireConstructor

     protected BeanWrapper autowireConstructor(
String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) { return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
}

可以看到这里还是委托给ConstructorResolver来执行的:

 public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) { // 封装BeanWrapperImpl并完成初始化
BeanWrapperImpl bw = new BeanWrapperImpl();
this.beanFactory.initBeanWrapper(bw); // 获取constructorToUse、argsHolderToUse、argsToUse属性
Constructor<?> constructorToUse = null; // 构造函数
ArgumentsHolder argsHolderToUse = null; // 构造参数
Object[] argsToUse = null; // 构造参数 // 确定构造参数
// 如果getBean()已传递,则直接使用
if (explicitArgs != null) {
argsToUse = explicitArgs;
} else {
// 尝试从缓存中获取
Object[] argsToResolve = null;
// 同步
synchronized (mbd.constructorArgumentLock) {
// 缓存中的构造函数或工厂方法
constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
if (constructorToUse != null && mbd.constructorArgumentsResolved) {
// Found a cached constructor...
// 缓存中的构造参数
argsToUse = mbd.resolvedConstructorArguments;
if (argsToUse == null) {
// 获取缓存中的构造函数参数 包括可见字段
argsToResolve = mbd.preparedConstructorArguments;
}
}
}
// 缓存中存在,则解析存储在BeanDefinition中的参数
// 如给定方法的构造函数 f(int,int),则通过此方法后就会把配置文件中的("1","1")转换为 (1,1)
// 缓存中的值可能是原始值也有可能是最终值
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
}
} // 没有缓存,则尝试从配置文件中获取参数
if (constructorToUse == null || argsToUse == null) {
// Take specified constructors, if any.
Constructor<?>[] candidates = chosenCtors;
// 如果chosenCtors未传入,则获取构造方法
if (candidates == null) {
Class<?> beanClass = mbd.getBeanClass();
try {
candidates = (mbd.isNonPublicAccessAllowed() ?
beanClass.getDeclaredConstructors() : beanClass.getConstructors());
} catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Resolution of declared constructors on bean Class [" + beanClass.getName() +
"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
}
}
// 创建bean
if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
Constructor<?> uniqueCandidate = candidates[0];
if (uniqueCandidate.getParameterCount() == 0) {
synchronized (mbd.constructorArgumentLock) {
mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
mbd.constructorArgumentsResolved = true;
mbd.resolvedConstructorArguments = EMPTY_ARGS;
}
bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS));
return bw;
}
} // 是否需要解析构造器
// Need to resolve the constructor.
boolean autowiring = (chosenCtors != null ||
mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
// 用于承载解析后的构造函数参数值
ConstructorArgumentValues resolvedValues = null; int minNrOfArgs;
if (explicitArgs != null) {
minNrOfArgs = explicitArgs.length;
} else {
// 从BeanDefinition中获取构造参数,也就是从配置文件中提取构造参数
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
resolvedValues = new ConstructorArgumentValues();
// 解析构造函数的参数
// 将该bean的构造函数参数解析为resolvedValues对象,其中会涉及到其他bean
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
} // 对构造函数进行排序处理
// public构造函数优先,参数数量降序,非public构造函数参数数量降序
AutowireUtils.sortConstructors(candidates); // 最小参数类型权重
int minTypeDiffWeight = Integer.MAX_VALUE;
Set<Constructor<?>> ambiguousConstructors = null;
LinkedList<UnsatisfiedDependencyException> causes = null; // 遍历所有构造函数
for (Constructor<?> candidate : candidates) {
// 获取该构造函数的参数类型
Class<?>[] paramTypes = candidate.getParameterTypes(); // 如果已经找到选用的构造函数且需要的参数个数大于当前构造函数参数个数,则break
if (constructorToUse != null && argsToUse != null && argsToUse.length > paramTypes.length) {
// Already found greedy constructor that can be satisfied ->
// do not look any further, there are only less greedy constructors left.
break;
}
// 参数个数不等,继续循环
if (paramTypes.length < minNrOfArgs) {
continue;
} // 参数持有者ArgumentsHolder对象
ArgumentsHolder argsHolder;
if (resolvedValues != null) {
try {
// 注释上获取参数名称
String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length);
if (paramNames == null) {
// 获取构造函数、方法参数的探测器
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
if (pnd != null) {
// 通过探测器获取构造函数的参数名称
paramNames = pnd.getParameterNames(candidate);
}
}
// 根据构造函数和构造参数,创建参数持有者ArgumentsHolder对象
argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
} catch (UnsatisfiedDependencyException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
}
// Swallow and try next constructor.
if (causes == null) {
causes = new LinkedList<>();
}
// 若产生UnsatisfiedDependencyException异常,则添加到causes中
causes.add(ex);
continue;
}
} else {
// Explicit arguments given -> arguments length must match exactly.
// 构造函数中没有参数,则continue
if (paramTypes.length != explicitArgs.length) {
continue;
}
// 根据explicitArgs创建ArgumentsHolder对象
argsHolder = new ArgumentsHolder(explicitArgs);
} // isLenientConstructorResolution 判断解析构造函数的时候是否以宽松模式还是严格模式
// 严格模式:解析构造函数时,必须所有的都需要匹配,否则抛出异常
// 宽松模式:使用具有"最接近的模式"进行匹配
// typeDiffWeight:类型差异权重
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
// Choose this constructor if it represents the closest match.
// 如果它代表着当前最接近的匹配则选择其作为构造函数
if (typeDiffWeight < minTypeDiffWeight) {
constructorToUse = candidate;
argsHolderToUse = argsHolder;
argsToUse = argsHolder.arguments;
minTypeDiffWeight = typeDiffWeight;
ambiguousConstructors = null;
} else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
if (ambiguousConstructors == null) {
ambiguousConstructors = new LinkedHashSet<>();
ambiguousConstructors.add(constructorToUse);
}
ambiguousConstructors.add(candidate);
}
} // 没有可执行的工厂方法,则抛出异常
if (constructorToUse == null) {
if (causes != null) {
UnsatisfiedDependencyException ex = causes.removeLast();
for (Exception cause : causes) {
this.beanFactory.onSuppressedException(cause);
}
throw ex;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Could not resolve matching constructor " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
} else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Ambiguous constructor matches found in bean '" + beanName + "' " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
ambiguousConstructors);
} if (explicitArgs == null && argsHolderToUse != null) {
// 将解析的构造函数加入缓存
argsHolderToUse.storeCache(mbd, constructorToUse);
}
}
// 创建bean对象,并设置到BeanWrapperImpl中
Assert.state(argsToUse != null, "Unresolved constructor arguments");
bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
return bw;
}

分析:

该函数可简单理解为是带有参数的构造方法来进行bean的初始化。可以看到该函数与【spring源码分析】IOC容器初始化(九)中分析的instantiateUsingFactoryMethod函数一样方法体都非常的大,如果理解了instantiateUsingFactoryMethod中的初始化过程,其实对autowireConstructor函数的理解就不是很难了,该函数主要是确定构造函数参数、构造函数然后调用相应的初始化策略对bean进行初始化,其大部分逻辑都与instantiateUsingFactoryMethod函数一致,如有不明之处,请移步到上篇文章,但需但需注意其中的instantiate是不一样的,这里我们来看下该函数。

instantiate

 private Object instantiate(
String beanName, RootBeanDefinition mbd, Constructor<?> constructorToUse, Object[] argsToUse) { try {
InstantiationStrategy strategy = this.beanFactory.getInstantiationStrategy();
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged((PrivilegedAction<Object>) () ->
strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse),
this.beanFactory.getAccessControlContext());
} else {
return strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
}
} catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean instantiation via constructor failed", ex);
}
}

分析:

这里对bean进行实例化,会委托SimpleInstantiationStrategy#instantiate方法进行实现:

 public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
final Constructor<?> ctor, Object... args) {
// 没有覆盖,则直接使用反射实例化即可
if (!bd.hasMethodOverrides()) {
if (System.getSecurityManager() != null) {
// use own privileged to change accessibility (when security is on)
// 设置构造方法,可访问
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
ReflectionUtils.makeAccessible(ctor);
return null;
});
}
// 通过BeanUtils直接使用构造器对象实例化bean对象
return BeanUtils.instantiateClass(ctor, args);
} else {
// CGLIB创建bean对象
return instantiateWithMethodInjection(bd, beanName, owner, ctor, args);
}
}

分析:

  • 如果该bean没有配置lookup-method、replace-method或者@LookUp注解,则直接通过反射的方式实例化bean对象即可。
  • 如果存在覆盖,则需要使用CGLIB进行动态代理,因为在创建代理的同时可将动态方法织入类中。

#1 通过反射实例化bean对象

 // BeanUtils
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
try {
// 设置构造方法,可访问
ReflectionUtils.makeAccessible(ctor);
// 使用构造方法创建对象
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
return KotlinDelegate.instantiateClass(ctor, args);
} else {
Class<?>[] parameterTypes = ctor.getParameterTypes();
Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
Object[] argsWithDefaultValues = new Object[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] == null) {
Class<?> parameterType = parameterTypes[i];
argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
} else {
argsWithDefaultValues[i] = args[i];
}
}
return ctor.newInstance(argsWithDefaultValues);
}
}
// 各种异常,最终统一抛出BeanInstantiationException异常
catch (InstantiationException ex) {
throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
} catch (IllegalAccessException ex) {
throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
} catch (IllegalArgumentException ex) {
throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
} catch (InvocationTargetException ex) {
throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
}
}

#2 通过CGLIB创建Bean对象,这里会通过其子类实现

 // CglibSubclassingInstantiationStrategy
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Constructor<?> ctor, Object... args) { // Must generate CGLIB subclass...
// 通过CGLIB生成一个子类对象
return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
}

分析:

首先创建一个CglibSubclassCreator对象,然后调用其instantiate进行初始化bean对象。

CglibSubclassingInstantiationStrategy#instantiate

 public Object instantiate(@Nullable Constructor<?> ctor, Object... args) {
// 通过cglib创建一个代理类
Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
Object instance;
// 没有构造器,通过BeanUtils使用默认构造器创建一个bean实例
if (ctor == null) {
instance = BeanUtils.instantiateClass(subclass);
} else {
try {
// 获取代理类对应的构造器对象,并实例化bean
Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
instance = enhancedSubclassConstructor.newInstance(args);
} catch (Exception ex) {
throw new BeanInstantiationException(this.beanDefinition.getBeanClass(),
"Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex);
}
}
// SPR-10785: set callbacks directly on the instance instead of in the
// enhanced class (via the Enhancer) in order to avoid memory leaks.
// 为了避免memory leaks异常,直接在bean实例上设置回调对象
Factory factory = (Factory) instance;
factory.setCallbacks(new Callback[]{NoOp.INSTANCE,
new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
return instance;
}

分析:

这里是通过CGLIB来创建bean对象,这部分内容后续分析AOP的时候再详细分析。

至此通过构造函数自动注入初始化bean对象分析完毕,下面来看使用默认构造函数初始化bean。

#2 AbstractAutowireCapableBeanFactory#instantiateBean

 protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
try {
Object beanInstance;
final BeanFactory parent = this;
// 安全模式
if (System.getSecurityManager() != null) {
// 获得InstantiationStrategy对象,并使用它创建bean对象
beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () -> getInstantiationStrategy().instantiate(mbd, beanName, parent),
getAccessControlContext());
} else {
// 获得InstantiationStrategy对象,并使用它创建bean对象
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
// 封装BeanWrapperImpl,并完成初始化
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
return bw;
} catch (Throwable ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
}
}

分析:

在经历过前面大方法体的分析后,再看该方法就简单得多了,该方法不需要构造参数,所以不需要进行复杂的确定构造参数、构造器等步骤,主要通过instantiate实例化对象后,注入BeanWrapper中,然后初始化BeanWrapper。

SimpleInstantiationStrategy#instantiate

 public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// Don't override the class with CGLIB if no overrides.
// 没有覆盖,直接使用反射实例化即可
if (!bd.hasMethodOverrides()) {
Constructor<?> constructorToUse;
synchronized (bd.constructorArgumentLock) {
// 获得构造方法 constructorToUse
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
final Class<?> clazz = bd.getBeanClass();
// 如果是接口,则抛出异常
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
// 从clazz中,获得构造方法
if (System.getSecurityManager() != null) {
constructorToUse = AccessController.doPrivileged(
(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
} else {
constructorToUse = clazz.getDeclaredConstructor();
}
// 标记resolvedConstructorOrFactoryMethod属性
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
} catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
// 通过BeanUtils直接使用构造器实例化bean对象
return BeanUtils.instantiateClass(constructorToUse);
} else {
// 通过CGLIB创建子类对象
// Must generate CGLIB subclass.
return instantiateWithMethodInjection(bd, beanName, owner);
}
}

分析:

该方法也比较简单,通过判断BeanDefinition是否有覆盖方法进行分支:通过反射或CGLIB来创建bean实例。

最后,如果以上分支还不满足,则会通过instantiateBean方法兜底来完成bean的实例化。

总结

终于把AbstractAutowireCapableBeanFactory#createBeanInstance方法大致分析完了,该方法就是选择合适的实例化策略来为bean创建实例对象,具体策略有:

  • Supplier回调方式。
  • 工厂方法初始化。
  • 构造函数自动注入初始化。
  • 默认构造函数初始化。

其中工厂方法初始化与构造函数自动注入初始化方式非常复杂,需要大量的经历来确定构造函数和构造参数。

至此createBeanInstance过程已经分析完毕,下面将介绍doCreateBean的其他处理流程。


by Shawn Chen,2019.04.25,下午。

【spring源码分析】IOC容器初始化(十)的更多相关文章

  1. SPRING源码分析:IOC容器

    在Spring中,最基本的IOC容器接口是BeanFactory - 这个接口为具体的IOC容器的实现作了最基本的功能规定 - 不管怎么着,作为IOC容器,这些接口你必须要满足应用程序的最基本要求: ...

  2. Spring源码解析-ioc容器的设计

    Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...

  3. spring源码分析---IOC(1)

    我们都知道spring有2个最重要的概念,IOC(控制反转)和AOP(依赖注入).今天我就分享一下spring源码的IOC. IOC的定义:直观的来说,就是由spring来负责控制对象的生命周期和对象 ...

  4. spring 源码之 ioc 容器的初始化和注入简图

    IoC最核心就是两个过程:IoC容器初始化和IoC依赖注入,下面通过简单的图示来表述其中的关键过程:

  5. Spring源码阅读-IoC容器解析

    目录 Spring IoC容器 ApplicationContext设计解析 BeanFactory ListableBeanFactory HierarchicalBeanFactory Messa ...

  6. Spring 源码剖析IOC容器(一)概览

    目录 一.容器概述 二.核心类源码解读 三.模拟容器获取Bean ======================= 一.容器概述 spring IOC控制反转,又称为DI依赖注入:大体是先初始化bean ...

  7. Spring源码解析-IOC容器的实现

    1.IOC容器是什么? IOC(Inversion of Control)控制反转:本来是由应用程序管理的对象之间的依赖关系,现在交给了容器管理,这就叫控制反转,即交给了IOC容器,Spring的IO ...

  8. Spring源码解析-IOC容器的实现-ApplicationContext

    上面我们已经知道了IOC的建立的基本步骤了,我们就可以用编码的方式和IOC容器进行建立过程了.其实Spring已经为我们提供了很多实现,想必上面的简单扩展,如XMLBeanFacroty等.我们一般是 ...

  9. Spring源码之IOC容器创建、BeanDefinition加载和注册和IOC容器依赖注入

    总结 在SpringApplication#createApplicationContext()执行时创建IOC容器,默认DefaultListableBeanFactory 在AbstractApp ...

随机推荐

  1. 【重磅】微软开源自动机器学习工具 - NNI

    [重磅]微软开源自动机器学习工具 - NNI 在机器学习建模时,除了准备数据,最耗时耗力的就是尝试各种超参组合,找到模型最佳效果的过程了.即使是对于有经验的算法工程师和数据科学家,有时候也很难把握其中 ...

  2. java日志框架log4j详细配置及与slf4j联合使用教程

    最后更新于2017年02月09日 一.log4j基本用法 首先,配置log4j的jar,maven工程配置以下依赖,非maven工程从maven仓库下载jar添加到“build path” <d ...

  3. java中的伪泛型---泛型擦除(不需要手工强转类型,却可以调用强转类型的方法)

    Java集合如Map.Set.List等所有集合只能存放引用类型数据,它们都是存放引用类型数据的容器,不能存放如int.long.float.double等基础类型的数据. 使用反射可以破解泛型T类型 ...

  4. mysql的学习笔记(五)

    1.子查询,出现在其他SQL语句的SELECT子句 SELECT * FROM t1 WHERE col1=(SELECT col2 FROM t2); 第一个SELECT称为外层查询,第二个称为子查 ...

  5. Tomcat的测试网页换成自己项目首页

    <Host name="localhost" appBase="webapps" unpackWARs="true" autoDepl ...

  6. java~gradle构建公用包并上传到仓库

    java~gradle构建公用包并上传到仓库 我们一般会把公用的代码放在一个包里,然后其它 项目可以直接使用,就像你使用第三方包一样! 仓库 存储包的地方叫做仓库,一般可以分为本地仓库和远程仓库,本地 ...

  7. 安装Phalcon报错:gcc: Internal error: Killed (program cc1)

    起因 安装Phalcon可以参考github上面的README.md 下面是我在阿里云ECS服务器上面执行命令的过程: # 安装依赖 sudo yum install php-devel pcre-d ...

  8. 为什么要抛弃Pact?如何快速实现契约测试(CDC)

    前言 在前几天的博客中,我转载了一篇文章,其中介绍了契约测试和pact是怎么实施的,的确很有帮助.但我经过研究,其实是pact本身也是有缺陷的,结合我近期在使用的服务型工具和我的实际情况,觉得实现契约 ...

  9. C# 委托基础1.0

    在C# 1.0中提出了一种新特性叫作:委托.委托本质上一种类型.是对特定方法的抽象,定义委托后,可以将方法封装,把方法当参数,传递 using System; using System.Collect ...

  10. @Data注解使用后在eclipse中get/set报错解决方法

    Maven项目中已经导入相关的lombok.jar包但是使用后仍提示无set/get方法 安装完成之后,请确认eclipse安装路径下是否多了一个lombok.jar包,并且其 配置文件eclipse ...