Spring IOC 容器源码分析 - 创建单例 bean 的过程
1. 简介
在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)
的实现逻辑。对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去创建,而是从缓存中获取。如果某个 bean 还未实例化,这个时候就无法命中缓存。此时,就要根据 bean 的配置信息去创建这个 bean 了。相较于getBean(String)
方法的实现逻辑,创建 bean 的方法createBean(String, RootBeanDefinition, Object[])
及其所调用的方法逻辑上更为复杂一些。关于创建 bean 实例的过程,我将会分几篇文章进行分析。本篇文章会先从大体上分析 createBean(String, RootBeanDefinition, Object[])
方法的代码逻辑,至于其所调用的方法将会在随后的文章中进行分析。
好了,其他的不多说,直接进入正题吧。
2. 源码分析
2.1 创建 bean 实例的入口
在正式分析createBean(String, RootBeanDefinition, Object[])
方法前,我们先来看看 createBean 方法是在哪里被调用的。如下:
public T doGetBean(...) {
// 省略不相关代码
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 省略不相关代码
}
上面是 doGetBean 方法的代码片段,从中可以发现 createBean 方法。createBean 方法被匿名工厂类的 getObject 方法包裹,但这个匿名工厂类对象并未直接调用 getObject 方法。而是将自身作为参数传给了getSingleton(String, ObjectFactory)
方法,那么我们接下来再去看看一下getSingleton(String, ObjectFactory) 方法的实现。如下:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized (this.singletonObjects) {
// 从缓存中获取单例 bean,若不为空,则直接返回,不用再初始化
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
/*
* 将 beanName 添加到 singletonsCurrentlyInCreation 集合中,
* 用于表明 beanName 对应的 bean 正在创建中
*/
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<Exception>();
}
try {
// 通过 getObject 方法调用 createBean 方法创建 bean 实例
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// 将 beanName 从 singletonsCurrentlyInCreation 移除
afterSingletonCreation(beanName);
}
if (newSingleton) {
/*
* 将 <beanName, singletonObject> 键值对添加到 singletonObjects 集合中,
* 并从其他集合(比如 earlySingletonObjects)中移除 singletonObject 记录
*/
addSingleton(beanName, singletonObject);
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
上面的方法逻辑不是很复杂,这里简单总结一下。如下:
- 先从 singletonObjects 集合获取 bean 实例,若不为空,则直接返回
- 若为空,进入创建 bean 实例阶段。先将 beanName 添加到 singletonsCurrentlyInCreation
- 通过 getObject 方法调用 createBean 方法创建 bean 实例
- 将 beanName 从 singletonsCurrentlyInCreation 集合中移除
- 将 <beanName, singletonObject> 映射缓存到 singletonObjects 集合中
从上面的分析中,我们知道了 createBean 方法在何处被调用的。那么接下来我们一起深入 createBean 方法的源码中,来看看这个方法具体都做了什么事情。
2.2 createBean 方法全貌
createBean 和 getBean 方法类似,基本上都是空壳方法,只不过 createBean 的逻辑稍微多点,多做了一些事情。下面我们一起看看这个方法的实现逻辑,如下:
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
if (logger.isDebugEnabled()) {
logger.debug("Creating instance of bean '" + beanName + "'");
}
RootBeanDefinition mbdToUse = mbd;
// 解析 bean 的类型
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
try {
// 处理 lookup-method 和 replace-method 配置,Spring 将这两个配置统称为 override method
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
try {
// 在 bean 初始化前应用后置处理,如果后置处理返回的 bean 不为空,则直接返回
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
// 调用 doCreateBean 创建 bean
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
上面的代码不长,代码的执行流程比较容易看出,这里罗列一下:
- 解析 bean 类型
- 处理 lookup-method 和 replace-method 配置
- 在 bean 初始化前应用后置处理,若后置处理返回的 bean 不为空,则直接返回
- 若上一步后置处理返回的 bean 为空,则调用 doCreateBean 创建 bean 实例
下面我会分节对第2、3和4步的流程进行分析,步骤1的详细实现大家有兴趣的话,就自己去看看吧。
2.2.1 验证和准备 override 方法
当用户配置了 lookup-method 和 replace-method 时,Spring 需要对目标 bean 进行增强。在增强之前,需要做一些准备工作,也就是 prepareMethodOverrides 中的逻辑。下面来看看这个方法的源码:
public void prepareMethodOverrides() throws BeanDefinitionValidationException {
MethodOverrides methodOverrides = getMethodOverrides();
if (!methodOverrides.isEmpty()) {
Set<MethodOverride> overrides = methodOverrides.getOverrides();
synchronized (overrides) {
// 循环处理每个 MethodOverride 对象
for (MethodOverride mo : overrides) {
prepareMethodOverride(mo);
}
}
}
}
protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
// 获取方法名为 mo.getMethodName() 的方法数量,当方法重载时,count 的值就会大于1
int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
// count = 0,表明根据方法名未找到相应的方法,此时抛出异常
if (count == 0) {
throw new BeanDefinitionValidationException(
"Invalid method override: no method with name '" + mo.getMethodName() +
"' on class [" + getBeanClassName() + "]");
}
// 若 count = 1,表明仅存在已方法名为 mo.getMethodName(),这意味着方法不存在重载
else if (count == 1) {
// 方法不存在重载,则将 overloaded 成员变量设为 false
mo.setOverloaded(false);
}
}
上面的源码中,prepareMethodOverrides
方法循环调用了prepareMethodOverride
方法,并没其他的太多逻辑。主要准备工作都是在 prepareMethodOverride 方法中进行的,所以我们重点关注一下这个方法。prepareMethodOverride 这个方法主要用于获取指定方法的方法数量 count,并根据 count 的值进行相应的处理。count = 0 时,表明方法不存在,此时抛出异常。count = 1 时,设置 MethodOverride 对象的overloaded
成员变量为 false。这样做的目的在于,提前标注名称mo.getMethodName()
的方法不存在重载,在使用 CGLIB 增强阶段就不需要进行校验,直接找到某个方法进行增强即可。
上面的方法没太多的逻辑,比较简单,就先分析到这里。
2.2.2 bean 实例化前的后置处理
后置处理是 Spring 的一个拓展点,用户通过实现 BeanPostProcessor 接口,并将实现类配置到 Spring 的配置文件中(或者使用注解),即可在 bean 初始化前后进行自定义操作。关于后置处理较为详细的说明,可以参考我的了一篇文章Spring IOC 容器源码分析系列文章导读,这里就不赘述了。下面我们来看看 createBean 方法中的后置处理逻辑,如下:
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
Object bean = null;
// 检测是否解析过,mbd.beforeInstantiationResolved 的值在下面的代码中会被设置
if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
Class<?> targetType = determineTargetType(beanName, mbd);
if (targetType != null) {
// 应用前置处理
bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
if (bean != null) {
// 应用后置处理
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
}
}
// 设置 mbd.beforeInstantiationResolved
mbd.beforeInstantiationResolved = (bean != null);
}
return bean;
}
protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// InstantiationAwareBeanPostProcessor 一般在 Spring 框架内部使用,不建议用户直接使用
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
// bean 初始化前置处理
Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
if (result != null) {
return result;
}
}
}
return null;
}
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
// bean 初始化后置处理
result = beanProcessor.postProcessAfterInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}
在 resolveBeforeInstantiation 方法中,当前置处理方法返回的 bean 不为空时,后置处理才会被执行。前置处理器是 InstantiationAwareBeanPostProcessor 类型的,该种类型的处理器一般用在 Spring 框架内部,比如 AOP 模块中的AbstractAutoProxyCreator
抽象类间接实现了这个接口中的 postProcessBeforeInstantiation
方法,所以 AOP 可以在这个方法中生成为目标类的代理对象。不过我在调试的过程中,发现 AOP 在此处生成代理对象是有条件的。一般情况下条件都不成立,也就不会在此处生成代理对象。至于这个条件为什么不成立,因 AOP 这一块的源码我还没来得及看,所以暂时还无法解答。等我看过 AOP 模块的源码后,我再来尝试分析这个条件。
2.2.3 调用 doCreateBean 方法创建 bean
这一节,我们来分析一下doCreateBean
方法的源码。在 Spring 中,做事情的方法基本上都是以do
开头的,doCreateBean 也不例外。那下面我们就来看看这个方法都做了哪些事情。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
/*
* BeanWrapper 是一个基础接口,由接口名可看出这个接口的实现类用于包裹 bean 实例。
* 通过 BeanWrapper 的实现类可以方便的设置/获取 bean 实例的属性
*/
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
// 从缓存中获取 BeanWrapper,并清理相关记录
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
/*
* 创建 bean 实例,并将实例包裹在 BeanWrapper 实现类对象中返回。createBeanInstance
* 中包含三种创建 bean 实例的方式:
* 1. 通过工厂方法创建 bean 实例
* 2. 通过构造方法自动注入(autowire by constructor)的方式创建 bean 实例
* 3. 通过无参构造方法方法创建 bean 实例
*
* 若 bean 的配置信息中配置了 lookup-method 和 replace-method,则会使用 CGLIB
* 增强 bean 实例。关于这个方法,后面会专门写一篇文章介绍,这里先说这么多。
*/
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 此处的 bean 可以认为是一个原始的 bean 实例,暂未填充属性
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
mbd.resolvedTargetType = beanType;
// 这里又遇到后置处理了,此处的后置处理是用于处理已“合并的 BeanDefinition”。关于这种后置处理器具体的实现细节就不深入理解了,大家有兴趣可以自己去看
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
/*
* earlySingletonExposure 是一个重要的变量,这里要说明一下。该变量用于表示是否提前暴露
* 单例 bean,用于解决循环依赖。earlySingletonExposure 由三个条件综合而成,如下:
* 条件1:mbd.isSingleton() - 表示 bean 是否是单例类型
* 条件2:allowCircularReferences - 是否允许循环依赖
* 条件3:isSingletonCurrentlyInCreation(beanName) - 当前 bean 是否处于创建的状态中
*
* earlySingletonExposure = 条件1 && 条件2 && 条件3
* = 单例 && 是否允许循环依赖 && 是否存于创建状态中。
*/
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 添加工厂对象到 singletonFactories 缓存中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
// 获取早期 bean 的引用,如果 bean 中的方法被 AOP 切点所匹配到,此时 AOP 相关逻辑会介入
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
Object exposedObject = bean;
try {
// 向 bean 实例中填充属性,populateBean 方法也是一个很重要的方法,后面会专门写文章分析
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
/*
* 进行余下的初始化工作,详细如下:
* 1. 判断 bean 是否实现了 BeanNameAware、BeanFactoryAware、
* BeanClassLoaderAware 等接口,并执行接口方法
* 2. 应用 bean 初始化前置操作
* 3. 如果 bean 实现了 InitializingBean 接口,则执行 afterPropertiesSet
* 方法。如果用户配置了 init-method,则调用相关方法执行自定义初始化逻辑
* 4. 应用 bean 初始化后置操作
*
* 另外,AOP 相关逻辑也会在该方法中织入切面逻辑,此时的 exposedObject 就变成了
* 一个代理对象了
*/
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 若 initializeBean 方法未改变 exposedObject 的引用,则此处的条件为 true。
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// 下面的逻辑我也没完全搞懂,就不分析了。见谅。
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
try {
// 注册销毁逻辑
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
上面的注释比较多,分析的应该比较详细的。不过有一部分代码我暂时没看懂,就不分析了,见谅。下面我们来总结一下 doCreateBean 方法的执行流程吧,如下:
- 从缓存中获取 BeanWrapper 实现类对象,并清理相关记录
- 若未命中缓存,则创建 bean 实例,并将实例包裹在 BeanWrapper 实现类对象中返回
- 应用 MergedBeanDefinitionPostProcessor 后置处理器相关逻辑
- 根据条件决定是否提前暴露 bean 的早期引用(early reference),用于处理循环依赖问题
- 调用 populateBean 方法向 bean 实例中填充属性
- 调用 initializeBean 方法完成余下的初始化工作
- 注册销毁逻辑
doCreateBean 方法的流程比较复杂,步骤略多。由此也可了解到创建一个 bean 还是很复杂的,这中间要做的事情繁多。比如填充属性、对 BeanPostProcessor 拓展点提供支持等。以上的步骤对应的方法具体是怎样实现的,本篇文章并不打算展开分析。在后续的文章中,我会单独写文章分析几个逻辑比较复杂的步骤。有兴趣的阅读的朋友可以稍微等待一下,相关文章本周会陆续进行更新。
3. 总结
到这里,createBean 方法及其所调用的方法的源码就分析完了。总的来说,createBean 方法还是比较复杂的,需要多看几遍才能理清一些头绪。由于 createBean 方法比较复杂,对于以上的源码分析,我并不能保证不出错。如果有写错的地方,还请大家指点迷津。毕竟当局者迷,作为作者,我很难意识到哪里写的有问题。
好了,本篇文章到此结束。谢谢阅读。
参考
附录:Spring 源码分析文章列表
Ⅰ. IOC
更新时间 | 标题 |
---|---|
2018-05-30 | Spring IOC 容器源码分析系列文章导读 |
2018-06-01 | Spring IOC 容器源码分析 - 获取单例 bean |
2018-06-04 | Spring IOC 容器源码分析 - 创建单例 bean 的过程 |
2018-06-06 | Spring IOC 容器源码分析 - 创建原始 bean 对象 |
2018-06-08 | Spring IOC 容器源码分析 - 循环依赖的解决办法 |
2018-06-11 | Spring IOC 容器源码分析 - 填充属性到 bean 原始对象 |
2018-06-11 | Spring IOC 容器源码分析 - 余下的初始化工作 |
Ⅱ. AOP
更新时间 | 标题 |
---|---|
2018-06-17 | Spring AOP 源码分析系列文章导读 |
2018-06-20 | Spring AOP 源码分析 - 筛选合适的通知器 |
2018-06-20 | Spring AOP 源码分析 - 创建代理对象 |
2018-06-22 | Spring AOP 源码分析 - 拦截器链的执行过程 |
Ⅲ. MVC
更新时间 | 标题 |
---|---|
2018-06-29 | Spring MVC 原理探秘 - 一个请求的旅行过程 |
2018-06-30 | Spring MVC 原理探秘 - 容器的创建过程 |
本文在知识共享许可协议 4.0 下发布,转载需在明显位置处注明出处
作者:田小波
本文同步发布在我的个人博客:http://www.tianxiaobo.com
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
Spring IOC 容器源码分析 - 创建单例 bean 的过程的更多相关文章
- Spring IOC 容器源码分析 - 获取单例 bean
1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...
- Spring IOC 容器源码分析 - 创建原始 bean 对象
1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...
- Spring IOC 容器源码分析 - 填充属性到 bean 原始对象
1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反 ...
- Spring IOC 容器源码分析 - 余下的初始化工作
1. 简介 本篇文章是"Spring IOC 容器源码分析"系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bea ...
- Spring IOC 容器源码分析 - 循环依赖的解决办法
1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...
- Spring IOC 容器源码分析系列文章导读
1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ...
- 最简 Spring IOC 容器源码分析
前言 BeanDefinition BeanFactory 简介 Web 容器启动过程 bean 的加载 FactoryBean 循环依赖 bean 生命周期 公众号 前言 许多文章都是分析的 xml ...
- Spring IOC 容器源码分析
声明!非原创,本文出处 Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 S ...
- Spring IOC 容器源码分析(转)
原文地址 Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢 ...
随机推荐
- mybatis 操作数据错误Truncated incorrect DOUBLE value: ''
网上查到遇到次错误造成的原因: UPDATE TSYS_ROLE_RIGHTSET ACTIVE_FLAG = '2' and UPDATE_PERSON = 'CaiYiHua' and UPDA ...
- autohotkey快捷键
;已经基本修复了输入带shift的时候跟输入法中英文切换之间的冲突 SetStoreCapslockMode, off SetKeyDelay, ^CapsLock:: #UseHook ;用这个和下 ...
- db2 解锁表
db2 set integrity for ACT_RU_VARIABLE immediate checked
- C#中隐式运行CMD命令行窗口的方法
using System; using System.Diagnostics; namespace Business { /// <summary> /// Command 的摘要说明. ...
- 在 Anaconda下解决国内安装tensorflow等下载慢和中断,出错,异常问题的一点思路
把镜像地址改为清华大学开源软件镜像站,打开 管理员身份打开cmd 输入conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/ ...
- 【转】linux 磁盘挂载
挂载好新硬盘后输入fdisk -l命令看当前磁盘信息 可以看到除了当前的第一块硬盘外还有一块sdb的第二块硬盘,然后用fdisk /dev/sdb 进行分区 进入fdisk命令,输入h可以看到该命令的 ...
- 威伦TK6070iQ触摸屏的使用
A.TK6070iQ只支持U盘互相倒腾. TK6070iQ有2个串口Com1 (232) Com2 (485) U盘上传 需要选择COM2(485),因为上传后是PLC与触摸屏通过485通讯,协议选s ...
- IntelliJ IDEA 2017版 Spring5 的RunnableFactoryBean配置
1.新建RunnableFactoryBean package com.spring4.pojo; import org.springframework.beans.factory.FactoryBe ...
- adb push init.rc /
http://blog.csdn.net/jumper511/article/details/28856249 修改Android init.rc文件后,需要将修改后的文件上传到手机,但是发下如下问题 ...
- MATLAB拟合正态分布
clear;clc;close all format compact %% 正态分布的拟合 % 生成随机数 num = 50; y = randn(1000,1); x = 1:num; y = hi ...