@Qualified

在spring中进行依赖注入的方式有两个注解可以使用,分别是 @Resource、@Autowired 两个注解其对应的功能分别是

  • Resource:默认按照名称进行装配,可以通过name属性指定名称,如果没有指定name属性,当注解写在字段上时,默认取字段名进行查找注入,如果写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配
  • Autowired: 默认按类型装配,默认情况下必须要求依赖对象必须存在。如果 Spring 没有其他提示,将会按照需要注入的变量名称来寻找合适的 bean

现在有个场景就是如果我一个接口下面有多个实现,我主要想注入标记了对应注解的实现类,上面的两个注解就不能单独进行使用了,需要进行搭配使用;例如:@LoadBalanced 注解实现负载均衡;

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {}

注意 @LoadBalanced 注解中包含了一个 @Qualifier 的注解,这个注解的用处就是可以在一个接口多个实现类中进行指定注入例如:spring cloud提供的对 RestTemplate 进行负载,下面我们看一下具体的源码是如何进行导入的

public class LoadBalancerAutoConfiguration {@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList();
}

spring工厂在实例化后对属性进行赋值时执行 InstantiationAwareBeanPostProcessor 接口的实现类

而进行属性注入的时候是通过 AutowiredAnnotationBeanPostProcessor 处理器在bean实例化之后对 @Autowired 注解进行属性赋值的

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {/*** 获取到对象内部中 是否有Autowired注解标注的属性* InjectionMetadata:* 包含一个list的 InjectedElement接口元素,会将 @Autowired注解标记的字段或者方法解析成对应的实现类*/InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {metadata.inject(bean, beanName, pvs);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs;}

下面的代码就是循环调用 InjectedElement 的方法

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Collection<InjectedElement> checkedElements = this.checkedElements;Collection<InjectedElement> elementsToIterate =(checkedElements != null ? checkedElements : this.injectedElements);if (!elementsToIterate.isEmpty()) {for (InjectedElement element : elementsToIterate) {element.inject(target, beanName, pvs);}}}

下面以 AutowiredFieldElement 为例子进行源码的解读。下面的代码可以看到最终解析是通过 resolveFieldValue() 的方法进行解析

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {//强转为字段对象Field field = (Field) this.member;Object value;//是否被缓存了,默认falseif (this.cached) {try {//开启了缓存直接解析已经缓存了的value值value = resolvedCachedArgument(beanName, this.cachedFieldValue);}catch (NoSuchBeanDefinitionException ex) {// Unexpected removal of target bean for cached argument -> re-resolvevalue = resolveFieldValue(field, bean, beanName);}}else {//通过字段、bean、bean名称去bean工厂中解析出对应的依赖值value = resolveFieldValue(field, bean, beanName);}if (value != null) {ReflectionUtils.makeAccessible(field);field.set(bean, value);}}

解析字段的依赖

@Nullableprivate Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {//依赖描述类,里面包括了 Method、Filed 的描述DependencyDescriptor desc = new DependencyDescriptor(field, this.required);//设置字段或者方法所属的bean对象类desc.setContainingClass(bean.getClass());//获取到的依赖bean的名称Set<String> autowiredBeanNames = new LinkedHashSet<>(1);Assert.state(beanFactory != null, "No BeanFactory available");//获取到spring工厂定义的类型转换器TypeConverter typeConverter = beanFactory.getTypeConverter();Object value;try {//bean工厂解析出依赖value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}}

当前方法对需要注入的依赖类型进行判断,最重要的是 doResolveDependency() 方法

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());//处理 Optionalif (Optional.class == descriptor.getDependencyType()) {return createOptionalDependency(descriptor, requestingBeanName);}//处理 ObjectFactory 类else if (ObjectFactory.class == descriptor.getDependencyType() ||ObjectProvider.class == descriptor.getDependencyType()) {return new DependencyObjectProvider(descriptor, requestingBeanName);}else if (javaxInjectProviderClass == descriptor.getDependencyType()) {return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);}else {//获取到是否是懒加载,通过 ContextAnnotationAutowireCandidateResolver 进行判断是否有 @Lazy注解,如果是懒加载会通过 ProxyFactory 创建一个动态代理类Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);if (result == null) {result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;}}

doResolveDependency() 方法中最主要调用的是下面两个方法

  • resolveMultipleBeans:解析list、array、map等依赖
  • findAutowireCandidates:解析单个bean的依赖方法

整个方法所做的事情就是,先检查依赖缓存中是否已经缓存了字段的依赖数据,如果有的话直接解析缓存数据,没有的话就先解析所需要的字段依赖是集合相关的还是单个bean,优先解析字段依赖的类型是 list、array、map,然后再解析单个

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {//设置了一下当前正在解析的注入参数,通过本地线程进行保存InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);try {Object shortcut = descriptor.resolveShortcut(this);if (shortcut != null) {return shortcut;}Class<?> type = descriptor.getDependencyType();/*** 通过当前指定的自动注入的接口器来解析需要解析的bean的名称,目前有两个实现类* QualifierAnnotationAutowireCandidateResolver:用于处理 @Value 、@Qualifier 注解* SimpleAutowireCandidateResolver,默认的解析器,并没有实现什么特殊的功能* ContextAnnotationAutowireCandidateResolver:继承至 QualifierAnnotationAutowireCandidateResolver* 这里回去找 @Value 注解是否存在*/Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);if (value != null) {//如果value不为空的话进行下面的的处理.....代码省略}//解析依赖的是否是map、list、数组等Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);if (multipleBeans != null) {return multipleBeans;}//获取到匹配的beanMap<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);//如果依赖都没有找找到,先判断是否是 required=trueif (matchingBeans.isEmpty()) {if (isRequired(descriptor)) {//抛出bean依赖没有找到的问题raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}return null;}String autowiredBeanName;Object instanceCandidate;//匹配的bean的个数如果大于1if (matchingBeans.size() > 1) {//查看是否标注了 @Primary注解、@priority,优先使用注入autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);if (autowiredBeanName == null) {if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);}else {return null;}}instanceCandidate = matchingBeans.get(autowiredBeanName);}else {Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();//如果只有一个直接获取key值autowiredBeanName = entry.getKey();instanceCandidate = entry.getValue();}if (autowiredBeanNames != null) {autowiredBeanNames.add(autowiredBeanName);}if (instanceCandidate instanceof Class) {//如果类型为class,直接去bean工厂中调用 getBean() 查询,如果没有查询到直接实例化instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);}Object result = instanceCandidate;if (result instanceof NullBean) {if (isRequired(descriptor)) {raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}result = null;}if (!ClassUtils.isAssignableValue(type, result)) {throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());}return result;}finally {ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);}}

resolveMultipleBeans() 方法解析多元素,其中会进行判断字段的数据类型是什么,array、map、collection、interface 等组装的方式不一样但是获取数据bean的方式都一样,都是通过 findAutowireCandidates() 方法去通过bean工厂获取数据

private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {//获取到当前字段需要依赖的类型Class<?> type = descriptor.getDependencyType();//处理 StreamDependencyDescriptor 类型的依赖描述器,这里一般是 DependencyDescriptorif (descriptor instanceof StreamDependencyDescriptor) {return stream;}//判断依赖的类型是否是数组类型else if (type.isArray()) {Class<?> componentType = type.getComponentType();ResolvableType resolvableType = descriptor.getResolvableType();//解析数组依赖的类型是什么Class<?> resolvedArrayType = resolvableType.resolve(type);if (resolvedArrayType != type) {componentType = resolvableType.getComponentType().resolve();}if (componentType == null) {return null;}//获取到适合的依赖,通过 MultiElementDescriptor 对依赖描述器封装成多元素描述器Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,new MultiElementDescriptor(descriptor));if (matchingBeans.isEmpty()) {return null;}if (autowiredBeanNames != null) {autowiredBeanNames.addAll(matchingBeans.keySet());}//通过 TypeConverter 来进行转换判断类型之间是否可以互相转换TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());Object result = converter.convertIfNecessary(matchingBeans.values(), resolvedArrayType);if (result instanceof Object[]) {Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);if (comparator != null) {Arrays.sort((Object[]) result, comparator);}}return result;}//集合类型并且是接口else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {....省略}else if (Map.class == type) {//如果要求的参数为map,首先获取到 Map<key,value> 泛型的类型,通过泛型key和value的类型到容器中查找对应的依赖....省略}else {return null;}}

findAutowireCandidates() 第一步就是直接通过依赖的类型去bean工厂中直接获取到对应名称的依赖,然后通过后面的 isAutowireCandidate() 方法进行判断是否存在 @Qualified 注解,所用到的

protected Map<String, Object> findAutowireCandidates(@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {//根据类型会回去到所有合适的bean的名称String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());//创建一个LinkedHashMapMap<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length);//首先从已经解析了的依赖缓存池中查找对应的值是否有for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {//还是先解析缓存,代码省略}//处理找到的适合的beanfor (String candidate : candidateNames) {//判断是否是自己引用自己,以及通过 isAutowireCandidate() 调用 QualifierAnnotationAutowireCandidateResolver.isAutowireCandidate() 判断是否有 @Qualifierif (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {//将查找出的bean名称通过工厂对象直接进行解析然后添加到 Map中addCandidateEntry(result, candidate, descriptor, requiredType);}}//只有上面步骤 以及解析的依赖和判断不含@Qualifier注解时,解析的依赖还是为空时走这个分支if (result.isEmpty()) {//判断要求的类是否是一个接口或者listboolean multiple = indicatesMultipleBeans(requiredType);// Consider fallback matches if the first pass failed to find anything...DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();for (String candidate : candidateNames) {//通过 AutowireCandidateResolver 类解析当前依赖是否有 @Qualifier注解通过名称进行注入 QualifierAnnotationAutowireCandidateResolverif (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {addCandidateEntry(result, candidate, descriptor, requiredType);}}if (result.isEmpty() && !multiple) {for (String candidate : candidateNames) {if (isSelfReference(beanName, candidate) &&(!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&isAutowireCandidate(candidate, fallbackDescriptor)) {addCandidateEntry(result, candidate, descriptor, requiredType);}}}}return result;}

isAutowireCandidate() 搜先判断bean定义是否存在,再判断bean是否存在于单例池中,最后调用是通过 AutowireCandidateResolver 的子实现类 ContextAnnotationAutowireCandidateResolver 进行判断是否存在 @Qualified 注解。

ContextAnnotationAutowireCandidateResolver 的配置是通过 AnnotatedBeanDefinitionReader 类在spring工厂初始化时就进行了设置

protected boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor, AutowireCandidateResolver resolver)throws NoSuchBeanDefinitionException {String bdName = BeanFactoryUtils.transformedBeanName(beanName);//bean定义中是否存在bean名称if (containsBeanDefinition(bdName)) {return isAutowireCandidate(beanName, getMergedLocalBeanDefinition(bdName), descriptor, resolver);}//是否存在于单例池中else if (containsSingleton(beanName)) {return isAutowireCandidate(beanName, new RootBeanDefinition(getType(beanName)), descriptor, resolver);}BeanFactory parent = getParentBeanFactory();if (parent instanceof DefaultListableBeanFactory) {return ((DefaultListableBeanFactory) parent).isAutowireCandidate(beanName, descriptor, resolver);}else if (parent instanceof ConfigurableListableBeanFactory) {return ((ConfigurableListableBeanFactory) parent).isAutowireCandidate(beanName, descriptor);}else {return true;}}
protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd,DependencyDescriptor descriptor, AutowireCandidateResolver resolver) {//转换bean名称String bdName = BeanFactoryUtils.transformedBeanName(beanName);//解析bean的classresolveBeanClass(mbd, bdName);if (mbd.isFactoryMethodUnique && mbd.factoryMethodToIntrospect == null) {new ConstructorResolver(this).resolveFactoryMethodIfPossible(mbd);}//创建一个  BeanDefinitionHolder 对象后面用于处理BeanDefinitionHolder holder = (beanName.equals(bdName) ?this.mergedBeanDefinitionHolders.computeIfAbsent(beanName,key -> new BeanDefinitionHolder(mbd, beanName, getAliases(bdName))) :new BeanDefinitionHolder(mbd, beanName, getAliases(bdName)));//通过子类 ContextAnnotationAutowireCandidateResolver 进行判断return resolver.isAutowireCandidate(holder, descriptor);}

ContextAnnotationAutowireCandidateResolver 继承至 QualifierAnnotationAutowireCandidateResolver;最重要的逻辑判断就是在 checkQualifiers() 方法中

public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {boolean match = super.isAutowireCandidate(bdHolder, descriptor);if (match) {//检查@Qualifiers注解match = checkQualifiers(bdHolder, descriptor.getAnnotations());//如果匹配了在判断方法的参数if (match) {MethodParameter methodParam = descriptor.getMethodParameter();if (methodParam != null) {Method method = methodParam.getMethod();if (method == null || void.class == method.getReturnType()) {match = checkQualifiers(bdHolder, methodParam.getMethodAnnotations());}}}}return match;}

先通过 isQualifier() 判断当前字段是否标记了 @Qualified 注解,然后再通过 checkQualifier() 去检查获取到的bean依赖对象中是否也含有 @Qualified 注解

protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {if (ObjectUtils.isEmpty(annotationsToSearch)) {return true;}SimpleTypeConverter typeConverter = new SimpleTypeConverter();//遍历获取到字段的注解for (Annotation annotation : annotationsToSearch) {Class<? extends Annotation> type = annotation.annotationType();boolean checkMeta = true;boolean fallbackToMeta = false;//判断注解是否有 @Qualifier 注解if (isQualifier(type)) {//判断获取到的依赖bean对象是否也有 @Qualifier注解if (!checkQualifier(bdHolder, annotation, typeConverter)) {fallbackToMeta = true;}else {checkMeta = false;}}......省略}return true;}

通过上面的方法我们可以看到,先获取到 字段中所有的注解,然后遍历注解;通过 checkQualifier() 每一个注解都进行判断是否有 @Qualified 例如下面的例子:

自动配置类中需要注入一个 List<RestTemplate> 的类型,下面配置了一个 RestTemplate 的bean对象,首先解析字段 restTemplates 需要依赖的是一个 List<RestTemplate> 从bean工厂中获取到 restTemplate1、restTemplate2 两个bean对象,但是现在字段 restTemplates 标记了 @LoadBalanced 注解而下面 checkQualifier() 的方法就是,先获取到 restTemplates 所有的注解 @LoadBalanced、@Autowired(required = false) 然后遍历判断是否存在 @Qualified ,我们知道 @LoadBalanced 中包含了这个注解所有就继续执行,通过当前注解去判断 restTemplate1、restTemplate2 两个bean是否也有 @LoadBalanced 如果有就符合条件

public class LoadBalancerAutoConfiguration {@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList();@Bean@LoadBalancedpublic RestTemplate restTemplate1() {return new RestTemplate();}@Beanpublic RestTemplate restTemplate2() {return new RestTemplate();}
}
protected boolean checkQualifier(BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {//获取到@LoadBalanced 注解Class<? extends Annotation> type = annotation.annotationType();//获取到bean定义RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();//根据注解的名称获取到适合的限定器AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName());if (qualifier == null) {//通过短名称来获取qualifier = bd.getQualifier(ClassUtils.getShortName(type));}if (qualifier == null) {// 先检查当前bean的注解中是否有对应的 @Qualified注解,例如:@LoadBalanced 其中包含@Qualified注解,这里就会进行判断对应的依赖属性是否也有 @LoadBalanced 注解Annotation targetAnnotation = getQualifiedElementAnnotation(bd, type);// Then, check annotation on factory method, if applicableif (targetAnnotation == null) {//解析工厂方法上面是否有对应的注解targetAnnotation = getFactoryMethodAnnotation(bd, type);}if (targetAnnotation == null) {RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd);if (dbd != null) {targetAnnotation = getFactoryMethodAnnotation(dbd, type);}}//如果上面获取到的目标注解还是为空的,那么直接获取到的class对象,通过class对象来进行对应注解的获取if (targetAnnotation == null) {if (getBeanFactory() != null) {try {Class<?> beanType = getBeanFactory().getType(bdHolder.getBeanName());if (beanType != null) {targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type);}}catch (NoSuchBeanDefinitionException ex) {}}if (targetAnnotation == null && bd.hasBeanClass()) {targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(bd.getBeanClass()), type);}}if (targetAnnotation != null && targetAnnotation.equals(annotation)) {return true;}}......省略return true;}

总结:spring通过在解析字段类型之后从bean工厂中获取到所有适合的依赖bean,然后通过 QualifierAnnotationAutowireCandidateResolver 解析器来判断依赖中是否存在对应的 Qualified 注解;而我们给 RestTemplate 标记的 @LoadBalanced 其实本身并没有进行处理,而是通过内部包含的 @Qualified 注解进行判断所依赖的bean是否也包含了 @LoadBalanced注解;注意点是spring先判断是否存在 @Qualified 注解,然后再拿 @LoadBalanced 注解去获取到bean的注解

Spring源码之@Qualified注解相关推荐

  1. Spring源码深度解析(郝佳)-学习-ASM 类字节码解析

    我们在Java字节码文件结构剖析(二)中己经对MyTest35_1这个类的字节码做了完整的解析,今天,我们来看看Spring的ASM技术是如何来解析Java类字节码的.话不多说,先上实例. MyTes ...

  2. Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean定义(一)

    我们在之前的博客 Spring源码深度解析(郝佳)-学习-ASM 类字节码解析 简单的对字节码结构进行了分析,今天我们站在前面的基础上对Spring中类注解的读取,并创建BeanDefinition做 ...

  3. Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析

      我们在看Spring Boot源码时,经常会看到一些配置类中使用了注解,本身配置类的逻辑就比较复杂了,再加上一些注解在里面,让我们阅读源码更加难解释了,因此,这篇博客主要对配置类上的一些注解的使用 ...

  4. Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)

    我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...

  5. Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)

    在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...

  6. Spring源码之Bean的注册(注解方式)

    1.创建AnnotationConfigApplicationContext AnnotationConfigApplicationContext context = new AnnotationCo ...

  7. Spring源码学习第七天==>解析配置注解类与BPP

    关键词: Spring解析配置类注解Bean Spring注册Bean后置增强器(BPP) Spring消息资源和监听器的初始化 一:Spring解析配置类注解Bean==>Configurat ...

  8. Spring源码解析之@Component注解的扫描

    阅读须知 Spring源码版本:4.3.8 文章中使用/* */注释的方法会做深入分析 正文 承接Spring源码解析之context:component-scan标签解析,下面就是扫描的流程: Cl ...

  9. Spring源码系列(十三)——Spring源码编译及详细注解

    文章目录 1. 环境搭建 2. 代码编译 2.1 编译代码 2.1.1 build.gradle 2.1.1.1 第一处 2.1.1.2 第二处 2.1.2 gradle.properties 2.1 ...

最新文章

  1. 构建空列表的两种法是_Python 基础3之列表
  2. Microsoft Accelerator for Windows Azure给我们的启示,由 TechStars 撰写
  3. 《网易编程题》疯狂队列
  4. Dapr + .NET 实战(八)服务监测
  5. php array分组,php数组分组简单例子
  6. React-Native 填坑之ListView(item更新)
  7. 【LeetCode笔记 - 每日一题】375. 猜数字游戏 II (Java、DFS、动态规划)
  8. 趣挨踢 | 用大数据扒一扒蔡徐坤的真假流量粉
  9. HG20615法兰数据注意事项
  10. 将常规项目转换为Maven项目
  11. set, unordered_set模板类
  12. jsp ---- JSTL
  13. Dart基础第13篇: 泛型、泛型方法 、泛型类、泛型接口
  14. python中sample()函数_PandasDataframe.sample()使用介绍
  15. python2安装biopython
  16. 维护设备的库存信息-SERIAL_EQBS_POST
  17. 360安全浏览器总是锁屏解锁后自启动打开网页,烦~
  18. 如何评价近几年顶会的超分,去噪,去模糊等图像复原文章?
  19. ctab法提取dna流程图_CTAB法原理(植物DNA提取经典方法)
  20. 系统错误无法连接到服务器失败怎么办啊,SQL Server无法连接到服务器怎么办

热门文章

  1. unity3d透明投影+红外线激光多点触摸+unity win7多点触摸案例
  2. Linux生成可执行文件
  3. 图解Bellman Ford算法
  4. OpenStack面试宝典
  5. 我的团长我的团第十八集
  6. redis启动报错解决
  7. 用类模板实现容器存储普通数据类型(类似于STL里面的vector)
  8. UE4 蓝图教程(一) 开始
  9. 互联网协议 — TCP/IP 分层体系结构
  10. JavaWeb学习笔记总结(一)