Spring 源码分析之 bean 依赖注入原理(注入属性)
     最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spring bean
的生命周期讲清楚,所以最后决定分解成几个模块来写,最后在写一篇文章把各个内容串联起来,这样可以讲的更细更深入不会犹豫篇幅而讲的太笼统。bean 生命周期所涉及的主要流程如下图所示。
     在上篇文章写了有bean实例创建相关的内容,感兴趣的朋友可以去看看Spring 源码分析之 bean 实例化原理这篇文章。
     本文想写bean 生命周期的第二阶段 bean 的依赖注入(注入属性)部分按下面几个步骤来讲解。
- Spring容器与依赖注入
- 什么时候会触发依赖注入?
- 关于依赖注入与属性注入的几点说明
- 解析Spring 依赖注入源码部分
- 总结
一. Spring容器与依赖注入
Spring最有名的高级特性非ioc莫属了,虽然ioc 不是本次要讨论的重点,但还是有必要说一下。对于Spring的ioc我不想过多教科书式的解释这名次,我也相信每个使用Spring的程序员都有自己的理解,只是有时很难把自己的理解清楚的解释给别人而已。下面我说说我自己的理解,有说错的地方欢迎大家指正。Spring ioc 至少要具备一下两点功能:
准备Bean 整个生命周期需要的数据
这一步是Spring 容器启动的时候会 定位我们的配置文件,加载文件,并解析成Bean的定义文件BeanDefinition来为下一步作准备,这个BeanDefinition会贯穿Spring 启动初始化的整个流程,非常重要,因为他是数据基础。
管理Bean的整个生命周期
- 需要具备创建一个Bean的功能
- 需要具备根据Bean与Bean之间的关系依赖注入功能(本次要讲的内容)
- 需要能够执行初始化方法以及销毁方法
有了以上几个功能之后Spring ioc 就能够控制bean的流程了,这不控制反转了么。而我们只需用注解或者配置文件配置bean的特性以及依赖关系即可。下面说一下有关ApplicationContext 和 BeanDefinition:
1. 核心容器ApplicationContext
    上述这些功能都可以由Spring容器(比如 ApplicationContext)来实现,Spring启动时会把所有需要的bean扫描并注册到容器里,在这个过程当中Spring会根据我们定义的bean之间的依赖关系来进行注入,依赖关系的维护方式有两种即XML配置文件或者注解,Spring启动时会把这些依赖关系转化成Spring能够识别的数据结构BeanDefinition,并根据它来进行bean的初始化,依赖注入等操作。下面看看一个简单的Spring容器如下图:
Spring 依赖注入的实现是由像ApplicationContext这种容器来实现的,右边的Map里存储这bean之间的依赖关系的定义BeanDefinition,比如OrderController依赖OrderService这种,具体定义下面介绍。
结论:BeanDefinition提供了原材料数据基础,而ApplicationContext 提供了流程的设计与实现的算法
2. Bean依赖关系的定义
我们需要为Spring容器提供所有bean的定义以及bean之间的依赖关系,从而进行bean的依赖注入通常有两种方式,XML配置或者注解,不管是那种最终都会解析成BeanDefinition。
通过XML配置Bean依赖关系
<beans xmlns="http://www.springframework.org/schema/beans">
    <!-- orderDao 不需要任何的依赖 -->
    <bean id="orderDao" class="spring.DI.OrderDao"/>
    <!-- orderService 需要依赖orderDao -->
    <bean id="orderService" class="spring.DI.OrderService">
        <property name="orderDao" ref="orderDao"/>
    </bean>
    <!-- orderController orderService 也间接依赖了orderDao -->
    <bean id="orderController" class="spring.DI.OrderController">
        <property name="orderService" ref="orderService"/>
    </bean>
</beans>
public class OrderController {
    private OrderService orderService;
    public OrderService getOrderService() {
        return orderService;
    }
    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }
}
这种注入方式叫做set 方法注入,只需xml配置 加上对引用的bean的get set方法即可
通过注解定义置Bean依赖关系
<context:component-scan base-package="xxx.yyy"/>
@Controller
public class OrderController {
    @Autowired
    private OrderService orderService;
    public OrderController() {
    }
}
@Service
public class OrderService {
    @Autowired
    private OrderDao orderDao;
    public OrderService() {
    }
}
@Repository
public class OrderDao {
    public OrderDao() {
    }
}
Spring 启动时会把我们的定义信息转化成Spring能看懂的BeanDefinition,然后就可以由容器来创建bean以及依赖注入了,具体依赖注入的时候对于配置文件和注解的处理手段还不同,这个一会儿在解释。
二. 什么时候会触发依赖注入?
- Spring 容器启动初始化的时候(所有单例非懒加载的bean)
- 懒加载(lazy-init)的bean 第一次进行getBean的时候
1.Spring 容器启动初始化的时候
ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        // 容器初始化入口
        refresh();
    }
}
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        prepareRefresh();
        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);
        // Allows post-processing of the bean factory in context subclasses.
        postProcessBeanFactory(beanFactory);
        // Invoke factory processors registered as beans in the context.
        invokeBeanFactoryPostProcessors(beanFactory);
        // Register bean processors that intercept bean creation.
        registerBeanPostProcessors(beanFactory);
        // Instantiate all remaining (non-lazy-init) singletons.
        // 初始化所有非 懒加载的bean!!!!
        finishBeanFactoryInitialization(beanFactory);
        // Last step: publish corresponding event.
        finishRefresh();
    }
  }
finishBeanFactoryInitialization(beanFactory);// 初始化所有非 懒加载的bean!!!
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
   	// Stop using the temporary ClassLoader for type matching.
   	beanFactory.setTempClassLoader(null);
   	// 此处省略多行与本次无关代码
   	// Instantiate all remaining (non-lazy-init) singletons.
   	beanFactory.preInstantiateSingletons();
   }
public void preInstantiateSingletons() throws BeansException {
   // 所有beanDefinition集合
   List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
   // 触发所有非懒加载单例bean的初始化
   for (String beanName : beanNames) {
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      // 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在Spring 容器
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
          // 判断是否是FactoryBean
         if (isFactoryBean(beanName)) {
             // 对FactoryBean的处理
         }else {
             // 如果是普通bean则进行初始化依赖注入,此 getBean(beanName)接下来触发的逻辑跟
             // context.getBean("beanName") 所触发的逻辑是一样的
            getBean(beanName);
         }
      }
   }
}
@Override
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}
2.懒加载(lazy-init)的bean 第一次进行getBean
懒加载的bean 第一次进行getBean的操作调用的也是同一个方法
@Override
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}
doCreateBean是依赖注入的入口,也是我们本次要谈的核心函数。该方法具体实现在AbstractAutowireCapableBeanFactory类,感兴趣的朋友可以进去看看调用链。下面才刚刚开始进入依赖注入源码阶段。
三. 关于依赖注入与属性注入的几点说明
依赖注入其实是属性注入的一种特殊类型,他的特殊之处在于他要注入的是Bean,同样由Spring管理的Bean,而不是其他的参数,如String,List,Set,Array这种。
普通的属性的值用 value  (类型包括  String list set map ...)
Bean类型的属性的引用 ref,这种注入属于依赖注入
四. 解析Spring 依赖注入源码部分
- 依赖注入实现的入口
- 注解形式注入的源码
- xml 配置形式注入的源码
1. 依赖注入实现的入口
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
	//第一步 创建bean实例 还未进行属性填充和各种特性的初始化
	BeanWrapper instanceWrapper = null;
	if (instanceWrapper == null) {
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
	Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
	Object exposedObject = bean;
	try {
	    // 第二步 进行依赖注入(注入属性)
	    populateBean(beanName, mbd, instanceWrapper);
	    if (exposedObject != null) {
	     // 第三步  执行bean的初始化方法
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	    }
	}catch (Throwable ex) {
	 //  抛相应的异常
	}
	return exposedObject;
}
我们这里需要关注的是第二步关于依赖注入这一块,下面这行代码
populateBean(beanName, mbd, instanceWrapper);
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
        // 所有的属性
	PropertyValues pvs = mbd.getPropertyValues();
	// 这里是处理自动装配类型的, autowire=byName 或者byType。如果不配置不走这个分支,xml或注解都可配
	if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
			mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
		pvs = newPvs;
	}
	// 后处理器是否已经准备好(后处理器会处理已@Autowired 形式来注入的bean, 有一个
	// 子类AutowiredAnnotationBeanPostProcessor来处理@Autowired注入的bean)
	boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
	// 是否需要依赖检查
	boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);
	if (hasInstAwareBpps || needsDepCheck) {
		PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
		if (hasInstAwareBpps) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor)bp;
					// 这里会处理对注解形式的注入 重点!!!!
					pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
					if (pvs == null) {
						return;
					}
				}
			}
		}
	}
    // 注入参数的方法(注解的Bean的依赖注入除外)
	applyPropertyValues(beanName, mbd, bw, pvs);
}
2. 注解形式注入的源码
// 后处理器是否已经准备好(后处理器会处理已@Autowired 形式来注入的bean, 有一个
// 子类AutowiredAnnotationBeanPostProcessor来处理@Autowired注入的bean)
boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
// 是否需要依赖检查
boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);
if (hasInstAwareBpps || needsDepCheck) {
	PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
	if (hasInstAwareBpps) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor)bp;
				// 这里会处理对注解形式的注入,比如 @Autowired注解 由类AutowiredAnnotationBeanPostProcessor来处理
				pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
				if (pvs == null) {
					return;
				}
			}
		}
	}
}
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        // 这里定义了把谁注入到哪里
	InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
	try {
	   // 进行注入
	   metadata.inject(bean, beanName, pvs);
	}catch (Throwable ex) {
		throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
	}
	return pvs;
}
InjectionMetadata在这个类里头封装了依赖的bean与被依赖的bean的信息,比如orderCcontroller 依赖orderService,需要把orderService 注入到orderController。下面贴一下我debug的图片
injectedElements 就是所有需要被注入的bean
protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable {
	if (this.isField) {
		Field field = (Field) this.member;
		ReflectionUtils.makeAccessible(field);
		field.set(target, getResourceToInject(target, requestingBeanName));
	}else {
		if (checkPropertySkipping(pvs)) {
		   return;
		}
		try {
		   Method method = (Method) this.member;
		   ReflectionUtils.makeAccessible(method);
		   method.invoke(target, getResourceToInject(target, requestingBeanName));
		}catch (InvocationTargetException ex) {
		  throw ex.getTargetException();
		}
	}
}
3. xml 配置形式注入的源码
applyPropertyValues(beanName, mbd, bw, pvs);
这个步骤主要做的就是把属性转换成相对应的类的属性类型,并最后注入到依赖的bean里头,由一下步骤组成:
- 判断是否已转换
- 进行转换
- 注入到bean
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
        MutablePropertyValues mpvs = null;
        List<PropertyValue> original;
        if (pvs instanceof MutablePropertyValues) {
            mpvs = (MutablePropertyValues) pvs;
            // 判断是否已转换,已经转换了则return
            if (mpvs.isConverted()) {
                // Shortcut: use the pre-converted values as-is.
                bw.setPropertyValues(mpvs);
                return;
            }
            original = mpvs.getPropertyValueList();
        }
        TypeConverter converter = getCustomTypeConverter();
        BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
        // Create a deep copy, resolving any references for values.
        List<PropertyValue> deepCopy = new ArrayList<PropertyValue>(original.size());
        boolean resolveNecessary = false;
        for (PropertyValue pv : original) {
            if (pv.isConverted()) {
                deepCopy.add(pv);
            } else {
                // 属性名 如(name,orderService)
                String propertyName = pv.getName();
                // 未转换前的值,稍后贴出debug时的图
                Object originalValue = pv.getValue();
                // 转换后的值,进行转换处理(重要!!!!)
                Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
                Object convertedValue = resolvedValue;
                //
                if (resolvedValue == originalValue) {
                    if (convertible) {
                        pv.setConvertedValue(convertedValue);
                    }
                    deepCopy.add(pv);
                } else if (convertible && originalValue instanceof TypedStringValue &&
                        !((TypedStringValue) originalValue).isDynamic() &&
                        !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
                    pv.setConvertedValue(convertedValue);
                    deepCopy.add(pv);
                } else {
                    resolveNecessary = true;
                    deepCopy.add(new PropertyValue(pv, convertedValue));
                }
            }
        }
        // 转换完成
        if (mpvs != null && !resolveNecessary) {
            mpvs.setConverted();
        }
        // 这里就是进行属性注入的地方,跟上面的inject方法类似
        try {
            bw.setPropertyValues(new MutablePropertyValues(deepCopy));
        } catch (BeansException ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", ex);
        }
    }
下面介绍上述步骤中的两个核心的流程:
- 进行转换操作,生成最终需要注入的类型的对象
- 进行注入操作
1.进行转换操作,生成最终需要注入的类型的对象
这个方法会返回一个我们最终要注入的一个属性对应类的一个对象
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
根据参数类型做具体的转换处理,参数类型包括 1.Bean 2.Array 3.List 4.Set 5.Map 6.String 等等。
public Object resolveValueIfNecessary(Object argName, Object value) {
     // Bean 类型
    if (value instanceof RuntimeBeanReference) {
        RuntimeBeanReference ref = (RuntimeBeanReference) value;
        return resolveReference(argName, ref);
    }
    else if (value instanceof ManagedArray) {
        // 处理数组
        return resolveManagedArray(argName, (List<?>) value, elementType);
    }
    else if (value instanceof ManagedList) {
        // 处理list
        return resolveManagedList(argName, (List<?>) value);
    }
    else if (value instanceof ManagedSet) {
        // 处理set
        return resolveManagedSet(argName, (Set<?>) value);
    }
    else if (value instanceof ManagedMap) {
        // 处理map
        return resolveManagedMap(argName, (Map<?, ?>) value);
    }
    else if (value instanceof TypedStringValue) {
    // 处理字符串
    }
    else {
        return evaluate(value);
    }
}
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
    try {
	String refName = ref.getBeanName();
	refName = String.valueOf(doEvaluate(refName));
	if (ref.isToParent()) {
		if (this.beanFactory.getParentBeanFactory() == null) {
			throw new BeanCreationException("");
		}
		return this.beanFactory.getParentBeanFactory().getBean(refName);
	}else {
		Object bean = this.beanFactory.getBean(refName);
		this.beanFactory.registerDependentBean(refName, this.beanName);
		return bean;
	}
    }catch (BeansException ex) {throw new BeanCreationException(this.beanDefinition.getResourceDescription(), this.beanName,
				"Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName,ex);
    }
}
Object bean = this.beanFactory.getBean(refName);
this.beanFactory.getParentBeanFactory().getBean(refName);
从sprig ioc 容器的双亲中获取bean(被依赖的bean),假如orderCcontroller依赖orderService,则从容器中获取orderService。这里有个关键点,也就是这个获取bean的过程也是一个依赖注入的过程,换句话说依赖注入是个递归的过程!!!!!!知道被依赖的bean不依赖任何bean。
- orderCcontroller 依赖 orderService 的操作会触发 orderService 依赖 orderDao的操作
2.进行注入操作
这一步是通过Java的反射机制根据set 方法把属性注入到bean里。
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
protected void setPropertyValue(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
    String propertyName = tokens.canonicalName;
    String actualName = tokens.actualName;
    if (tokens.keys != null) {
     // 处理集合类型
    }
    else {
        // 对非集合类型的处理
        AbstractNestablePropertyAccessor.PropertyHandler ph = getLocalPropertyHandler(actualName);
        Object oldValue = null;
        try {
            Object originalValue = pv.getValue();
            Object valueToApply = originalValue;
            if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
                if (pv.isConverted()) {
                    valueToApply = pv.getConvertedValue();
                }else {
                    if (isExtractOldValueForEditor() && ph.isReadable()) {
                        try {
                            oldValue = ph.getValue();
                        }catch (Exception ex) {}
                    }
                    valueToApply = convertForProperty(propertyName, oldValue, originalValue, ph.toTypeDescriptor());
                }
                pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
            }
             // 通过反射注入
            ph.setValue(object, valueToApply);
            }
        }
    }
 public void setValue(final Object object, Object valueToApply) throws Exception {
    final Method writeMethod = this.pd.getWriteMethod();
    if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    writeMethod.setAccessible(true);
                    return null;
                }
            });
        } else {
            writeMethod.setAccessible(true);
        }
    }
    final Object value = valueToApply;
    if (System.getSecurityManager() != null) {
    } else {
        // 通过反射 用set 方法注入属性
        writeMethod.invoke(getWrappedInstance(), value);
    }
}
总结
本文主要写了一下几点:
1.依赖注入这个步骤是整个Spring ioc 的一部分以及一个功能
2.依赖注入是属性注入的一种,区别在于这个属性是由Spring管理的bean
3.触发依赖注入的时机
4.两种依赖注入方式 配置文件以及注解
5.源码部分
欢迎大家提出改进点,小弟入行不久功力浅。
参考:
《Spring 技术内幕》
《Spring 源码深度剖析》
Spring 源码分析之 bean 依赖注入原理(注入属性)的更多相关文章
- Spring源码分析之Bean的创建过程详解
		前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ... 
- Spring源码分析之循环依赖及解决方案
		Spring源码分析之循环依赖及解决方案 往期文章: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostPro ... 
- 【Spring源码分析】Bean加载流程概览
		代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ... 
- 【Spring源码分析】Bean加载流程概览(转)
		转载自:https://www.cnblogs.com/xrq730/p/6285358.html 代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. ... 
- Spring源码分析:Bean加载流程概览及配置文件读取
		很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ... 
- Spring 源码分析之 bean 实例化原理
		本次主要想写spring bean的实例化相关的内容.创建spring bean 实例是spring bean 生命周期的第一阶段.bean 的生命周期主要有如下几个步骤: 创建bean的实例 给实例 ... 
- 【转载】Spring 源码分析之 bean 实例化原理
		本次主要想写spring bean的实例化相关的内容.创建spring bean 实例是spring bean 生命周期的第一阶段.bean 的生命周期主要有如下几个步骤: 创建bean的实例 给实例 ... 
- Spring源码分析之Bean的加载流程
		spring版本为4.3.6.RELEASE 不管是xml方式配置bean还是基于注解的形式,最终都会调用AbstractApplicationContext的refresh方法: @Override ... 
- 【Spring源码分析】非懒加载的单例Bean初始化过程(上篇)
		代码入口 上文[Spring源码分析]Bean加载流程概览,比较详细地分析了Spring上下文加载的代码入口,并且在AbstractApplicationContext的refresh方法中,点出了f ... 
随机推荐
- EL表达式与标签库
			https://blog.csdn.net/panhaigang123/article/details/78428567 
- python安装scrapy等库需要c++ 14.0 下载链接放这里
			百度网盘下载地址:https://pan.baidu.com/s/1zZ7oKSuniABh1y7p0YahgA 或扫描二维码: 
- scrapy -->CrawlSpider 介绍
			scrapy -->CrawlSpider 介绍 1.首先,通过crawl 模板新建爬虫: scrapy genspider -t crawl lagou www.lagou.com 创建出来的 ... 
- 【mac上安装&配置&使用git】
			转自:https://www.jianshu.com/p/7edb6b838a2e 目录 安装git 创建ssh key.配置git 提交本地项目到GitHub 一.安装Git MAC 上安装Git主 ... 
- 65. Valid Number 判断字符串是不是数字
			[抄题]: Validate if a given string is numeric. Some examples:"0" => true" 0.1 " ... 
- 互联网公司的面试官是如何360°无死角考察候选人的?[z]
			[z]https://juejin.im/post/5c0e47ebf265da614e2be9a7 一.写在前面 最近收到不少读者反馈,说自己在应聘一些中大型互联网公司的Java工程师岗位时遇到了不 ... 
- Linux-echo、cat命令详解(14)
			echo:显示一段文字 比如: echo hello,串口上就显示hello echo hello > /dev/tty1, LCD上便显示hello字段 cat:查看一个文件的内容 比如: c ... 
- C# 检测证书是否安装、 安装证书
			检测是否存在指定的证书: /// <summary> /// 检测是否存在指定的证书 /// </summary> /// <returns></return ... 
- python 2与python3 区别
			源码区别 python3:python2 a) py3 优美简单清晰. b) py2:源码重复,混乱,不规范,冗(rong)余(不需要特多,啰嗦). test a) py3:可以中文也可以英文( ... 
- JAVAC 命令详解
			转自:http://jeffchen.iteye.com/blog/395671 结构 javac [ options ] [ sourcefiles ] [ @files ] 参数可按任意次序排列. ... 
