Spring IOC:BeanDefinition加载注册流程(转)
BeanFactory接口体系
以DefaultListableBeanFactory为例梳理一下BeanFactory接口体系的细节
主要接口、抽象类的作用如下:
BeanFactory(根据注册的bean定义来生产bean的功能)
BeanRegistry(bean定义的注册功能)
BeanDefinition(bean的定义信息)
BeanFactory
BeanFactory:用于访问Spring bean容器的根接口,提供多个重载的getBean方法来获取注册到容器中的bean的实例
HierarchicalBeanFactory:为Spring bean 容器提供父子容器的上下层级关系的能力
ListableBeanFactory:提供遍历Spring bean容器中bean的能力但其方法只检查容器内部bean的定义,而不会真正的实例化bean;并且不会包含其父容器中的bean定义
ConfigurableBeanFactory:提供遍历Spring bean容器的能力,比如向container中增加BeanPostProcessor
AutowireCapableBeanFactory:提供自动注入bean属性的能力,以及其他框架的集成代码可以利用这个接口来连接和填充Spring无法控制的现有bean实例生命周期
BeanRegistry
AliasRegistry:用于管理bean别名的接口
BeanDefinitionRegistry:提供注册BeanDefinition的能力
BeanRegistry
AliasRegistry:用于管理bean别名的接口
BeanDefinitionRegistry:提供注册BeanDefinition的能力
BeanDefinition
AnnotatedBeanDefinition:注解的元数据
RootBeanDefinition:在Spring bean容器运行期间,通过合并注册到容器中的bean定义生成的bean元数据
总结
在spring的容器接口体系中,我们可以使用原材料、工厂、生产线操作工人、最终产品的关系来类比BeanDefinition、BeanFactory、BeanRegistry、bean实例。 BeanRegistry提供能力将BeanDefinition注册到BeanFactory中,BeanFactory通过其内部的生产线来生成bean实例
BeanDefinition的接口实现类体系
以AnnotatedGenericBeanDefinition为例梳理一下BeanDefinition接口实现类体系
Metadata元数据部分
Metadata元数据部分在其内部包装了需要注册到BeanFactory的类的信息
类图
类关系说明
ClassMetadata,抽象出类的元数据的接口。提供获取类名、判断是否为接口类、是否为注解类、是否为抽象类等功能;
AnnotatedTypeMetadata,定义了接口,用于访问AnnotationMetadata、MethodMetadata这两个类的注解。提供判断是否被指定注解标记、获取指定注解的属性等功能;
AnnotationMetadata,定义了接口,用于访问指定类的注解;如getMetaAnnotationTypes(String annotationName)用于获取指定注解annotationName的元注解集合
StandardClassMetadata,用标准的反射功能实现了ClassMetadata类
StandardAnnotationMetadata,扩展StandardClassMetadata类并实现AnnotationMetadata接口
BeanDefinition部分
BeanDefinition作为注册到BeanFactory中的载体,在具体的实现类中,持有metadata实例。
类图
类关系说明
AttributeAccessor,定义设置和获取属性元数据的接口;
AttributeAccessorSupport,在内部通过LinkedHashMap实现了AttributeAccessor接口;
BeanMetadataElement,持有一个source,具体用途待考究;
BeanMetadataAttribute,实现BeanMetadataElement接口,在其内部以key-value的形式持有bean definition的属性;
BeanMetadataAttributeAccessor,实现BeanMetadataElement接口,并重写部分AttributeAccessorSupport的接口,用于设置和获取BeanMetadataElement;
BeanDefinition,一个BeanDefinition对象用于描述一个bean instance,其中拥有属性值、构造器属性值以及更多由其子类提供的信息;
AbstractBeanDefinition:实现了BeanDefinition接口,提供了设置和获取bean definition中的各个属性(即类的各种属性数据)
AnnotatedBeanDefinition,提供接口用于获取被包装的类的元数据
GenericBeanDefinition,提供parent bean的设置功能
AnnotatedGenericBeanDefinition,扩展自GenericBeanDefinition,实现AnnotatedBeanDefinition提供暴露注解元数据的支持功能
注册注解配置类流程
主流程
源码分析
1 public class AnnotatedBeanDefinitionReader {
2
3 // 省略部分代码
4 public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
5 AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
6 if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
7 return;
8 }
9 // 解析@Scope注解,获取bean的作用域配置信息
10 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
11 abd.setScope(scopeMetadata.getScopeName());
12 String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
13 // 解析通用注解,如@Lazy等
14 AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
15 // 定义qualifier信息,主要涉及到后续指定bean注入的注解@Qualifier
16 if (qualifiers != null) {
17 for (Class<? extends Annotation> qualifier : qualifiers) {
18 if (Primary.class == qualifier) {
19 abd.setPrimary(true);
20 }
21 else if (Lazy.class == qualifier) {
22 abd.setLazyInit(true);
23 }
24 else {
25 abd.addQualifier(new AutowireCandidateQualifier(qualifier));
26 }
27 }
28 }
29
30 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
31 // 根据ScopeMetadata生成对应的Scope代理
32 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
33 // 实际bean的注入,在registry内部用一个ConcurrentHashMap持有了beandefinition信息
34 BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
35 }
36
37 }
解析@Scope的流程,分析spring解析注解类属性值的流程
主流程
源码分析
1 // @Scope注解的解析器
2 public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver {
3
4 // 解析@Scope注解,构造ScopeMetadata实例,持有bean作用域的配置信息
5 @Override
6 public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
7 ScopeMetadata metadata = new ScopeMetadata();
8 if (definition instanceof AnnotatedBeanDefinition) {
9 AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
10 // 获取指定注解的属性值对,此处为@Scope注解
11 AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
12 annDef.getMetadata(), this.scopeAnnotationType);
13 // 如果属性不为null,则根据属性值对修改ScopeMetadata的值
14 if (attributes != null) {
15 metadata.setScopeName(attributes.getString("value"));
16 ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
17 if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
18 proxyMode = this.defaultProxyMode;
19 }
20 metadata.setScopedProxyMode(proxyMode);
21 }
22 }
23 return metadata;
24 }
25
26 }
27
28 // 注解配置信息的辅助工具类
29 public class AnnotationConfigUtils {
30
31 // 获取metadata中,annotationClass注解类型的属性值,用AnnotationAttributes(继承自LinkedHashMap,额外保存了注解类型等信息)持有
32 static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, Class<?> annotationClass) {
33 return attributesFor(metadata, annotationClass.getName());
34 }
35
36 // 获取metadata中,annotationClass注解类型的属性值,用AnnotationAttributes(继承自LinkedHashMap,额外保存了注解类型等信息)持有
37 static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, String annotationClassName) {
38 return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annotationClassName, false));
39 }
40
41 }
42
43 // 持有类的元数据
44 public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata {
45
46 // 获取指定注解名annotationName中的属性值对
47 @Override
48 public Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
49 return (this.annotations.length > 0 ? AnnotatedElementUtils.getMergedAnnotationAttributes(
50 getIntrospectedClass(), annotationName, classValuesAsString, this.nestedAnnotationsAsMap) : null);
51 }
52
53 }
54
55 // 用于在AnnotatedElement上查找注解、元注解、可重复注解的工具类
56 public class AnnotatedElementUtils {
57
58 public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
59 String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
60
61 Assert.hasLength(annotationName, "'annotationName' must not be null or empty");
62 // 根据注解名查找注解的属性值对
63 AnnotationAttributes attributes = searchWithGetSemantics(element, null, annotationName,
64 new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap));
65
66 // 处理注解别名
67 AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap);
68 return attributes;
69 }
70
71 private static <T> T searchWithGetSemantics(AnnotatedElement element,
72 Class<? extends Annotation> annotationType, String annotationName, Processor<T> processor) {
73 // 将查找工作,转发给processor处理(Processor -> MergedAnnotationAttributesProcessor)
74 return searchWithGetSemantics(element, annotationType, annotationName, null, processor);
75 }
76
77 private static <T> T searchWithGetSemantics(AnnotatedElement element,
78 Class<? extends Annotation> annotationType, String annotationName,
79 Class<? extends Annotation> containerType, Processor<T> processor) {
80
81 try {
82 // 进行第一层查找(metaDepth=0)
83 return searchWithGetSemantics(element, annotationType, annotationName,
84 containerType, processor, new HashSet<AnnotatedElement>(), 0);
85 }
86 catch (Throwable ex) {
87 AnnotationUtils.rethrowAnnotationConfigurationException(ex);
88 throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
89 }
90 }
91
92 private static <T> T searchWithGetSemantics(AnnotatedElement element,
93 Class<? extends Annotation> annotationType, String annotationName,
94 Class<? extends Annotation> containerType, Processor<T> processor,
95 Set<AnnotatedElement> visited, int metaDepth) {
96
97 Assert.notNull(element, "AnnotatedElement must not be null");
98
99 if (visited.add(element)) {
100 try {
101 // Start searching within locally declared annotations
102 List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations());
103 // 转发给重载方法,进行实际的查找操作
104 T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations,
105 annotationType, annotationName, containerType, processor, visited, metaDepth);
106 if (result != null) {
107 return result;
108 }
109
110 if (element instanceof Class) { // otherwise getAnnotations does not return anything new
111 List<Annotation> inheritedAnnotations = new ArrayList<Annotation>();
112 for (Annotation annotation : element.getAnnotations()) {
113 if (!declaredAnnotations.contains(annotation)) {
114 inheritedAnnotations.add(annotation);
115 }
116 }
117
118 // Continue searching within inherited annotations
119 result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations,
120 annotationType, annotationName, containerType, processor, visited, metaDepth);
121 if (result != null) {
122 return result;
123 }
124 }
125 }
126 catch (Throwable ex) {
127 AnnotationUtils.handleIntrospectionFailure(element, ex);
128 }
129 }
130
131 return null;
132 }
133
134 // 执行实际的注解属性查找功能
135 private static <T> T searchWithGetSemanticsInAnnotations(AnnotatedElement element,
136 List<Annotation> annotations, Class<? extends Annotation> annotationType,
137 String annotationName, Class<? extends Annotation> containerType,
138 Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
139
140 // Search in annotations
141 // 遍历注解列表
142 for (Annotation annotation : annotations) {
143 Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
144 // 只处理非JDK内置的注解
145 if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
146 // 满足以下任意条件,需要调用processor.process(element, annotation, metaDepth)方法进行属性值的查找工作
147 // 1. 如果当前循环的注解,为我们指定的注解类型
148 // 2. 如果当前循环的注解,为我们指定的注解名称
149 // 3. 始终调用processor,即processor.alwaysProcesses()返回true
150 if (currentAnnotationType == annotationType ||
151 currentAnnotationType.getName().equals(annotationName) ||
152 processor.alwaysProcesses()) {
153 // 查找注解属性值
154 T result = processor.process(element, annotation, metaDepth);
155 if (result != null) {
156
157 if (processor.aggregates() && metaDepth == 0) {
158 // 聚合查找结果
159 processor.getAggregatedResults().add(result);
160 }
161 else {
162 return result;
163 }
164 }
165 }
166 // Repeatable annotations in container?
167 else if (currentAnnotationType == containerType) {
168 for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
169 T result = processor.process(element, contained, metaDepth);
170 if (result != null) {
171 // No need to post-process since repeatable annotations within a
172 // container cannot be composed annotations.
173 processor.getAggregatedResults().add(result);
174 }
175 }
176 }
177 }
178 }
179
180 // Recursively search in meta-annotations
181 for (Annotation annotation : annotations) {
182 Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
183 if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
184 T result = searchWithGetSemantics(currentAnnotationType, annotationType,
185 annotationName, containerType, processor, visited, metaDepth + 1);
186 if (result != null) {
187 processor.postProcess(element, annotation, result);
188 if (processor.aggregates() && metaDepth == 0) {
189 processor.getAggregatedResults().add(result);
190 }
191 else {
192 return result;
193 }
194 }
195 }
196 }
197
198 return null;
199 }
200
201 }
202
203 private static class MergedAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
204
205 @Override
206 public AnnotationAttributes process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
207 return AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation,
208 this.classValuesAsString, this.nestedAnnotationsAsMap);
209 }
210
211 }
212
213
214 public abstract class AnnotationUtils {
215
216 // 将注解属性值包装为AnnotationAttributes,返回给上层调用
217 static AnnotationAttributes retrieveAnnotationAttributes(Object annotatedElement, Annotation annotation,
218 boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
219
220 Class<? extends Annotation> annotationType = annotation.annotationType();
221 AnnotationAttributes attributes = new AnnotationAttributes(annotationType);
222
223 for (Method method : getAttributeMethods(annotationType)) {
224 try {
225 Object attributeValue = method.invoke(annotation);
226 Object defaultValue = method.getDefaultValue();
227 if (defaultValue != null && ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
228 attributeValue = new DefaultValueHolder(defaultValue);
229 }
230 attributes.put(method.getName(),
231 adaptValue(annotatedElement, attributeValue, classValuesAsString, nestedAnnotationsAsMap));
232 }
233 catch (Throwable ex) {
234 if (ex instanceof InvocationTargetException) {
235 Throwable targetException = ((InvocationTargetException) ex).getTargetException();
236 rethrowAnnotationConfigurationException(targetException);
237 }
238 throw new IllegalStateException("Could not obtain annotation attribute value for " + method, ex);
239 }
240 }
241
242 return attributes;
243 }
244
245 }
基于注解配置,注册bean的原理
在spring的高版本中,官方建议开发者使用java code的配置方式。其原理主要是利用ConfigurationClassPostProcessor类来进行解析。执行的时机发生在容器启动后,调用invokeBeanFactoryPostProcessors()方法这一步。
主流程
源码分析
1 public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
2 PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
3
4 public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
5 List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
6 String[] candidateNames = registry.getBeanDefinitionNames();
7
8 // 遍历beanfactory中所有已注册的bean
9 for (String beanName : candidateNames) {
10 BeanDefinition beanDef = registry.getBeanDefinition(beanName);
11 // 判断是否为处理过的full配置类
12 if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
13 // 判断是否为处理过的lite配置类
14 ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
15 if (logger.isDebugEnabled()) {
16 logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
17 }
18 }
19 // 判断是否为配置类(标注了@Configuration、@Component、@ComponentScan、@Import、@ImportResource)
20 // 为full配置类时,为beanDef增加键为org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass,值为full的attribute
21 // 为lite配置类时,为beanDef增加键为org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass,值为lite的attribute
22 else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
23 configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
24 }
25 }
26
27 // Return immediately if no @Configuration classes were found
28 if (configCandidates.isEmpty()) {
29 return;
30 }
31
32 // Sort by previously determined @Order value, if applicable
33 // 配置类可以按照顺序加载
34 Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
35 @Override
36 public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
37 int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
38 int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
39 return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
40 }
41 });
42
43 // Detect any custom bean name generation strategy supplied through the enclosing application context
44 SingletonBeanRegistry sbr = null;
45 if (registry instanceof SingletonBeanRegistry) {
46 sbr = (SingletonBeanRegistry) registry;
47 if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
48 BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
49 this.componentScanBeanNameGenerator = generator;
50 this.importBeanNameGenerator = generator;
51 }
52 }
53
54 // Parse each @Configuration class
55 ConfigurationClassParser parser = new ConfigurationClassParser(
56 this.metadataReaderFactory, this.problemReporter, this.environment,
57 this.resourceLoader, this.componentScanBeanNameGenerator, registry);
58
59 Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
60 Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
61 do {
62 // 解析配置类,完成这一步流程后,在其内部对各种配置信息,包装为一个ConfigurationClass的集合
63 // 在加载bean的过程中,实际上也是对这个集合进行各种操作,如:从@Bean方法加载bean、@Import导入配置等等
64 parser.parse(candidates);
65 parser.validate();
66
67 Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
68 configClasses.removeAll(alreadyParsed);
69
70 // Read the model and create bean definitions based on its content
71 if (this.reader == null) {
72 this.reader = new ConfigurationClassBeanDefinitionReader(
73 registry, this.sourceExtractor, this.resourceLoader, this.environment,
74 this.importBeanNameGenerator, parser.getImportRegistry());
75 }
76 // 对ConfigurationClassParser持有的配置信息集合进行bean的加载。
77 // 至此,需要注册到IOC容器的所有bean都已注册完毕
78 this.reader.loadBeanDefinitions(configClasses);
79 alreadyParsed.addAll(configClasses);
80
81 candidates.clear();
82 if (registry.getBeanDefinitionCount() > candidateNames.length) {
83 String[] newCandidateNames = registry.getBeanDefinitionNames();
84 Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
85 Set<String> alreadyParsedClasses = new HashSet<String>();
86 for (ConfigurationClass configurationClass : alreadyParsed) {
87 alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
88 }
89 for (String candidateName : newCandidateNames) {
90 if (!oldCandidateNames.contains(candidateName)) {
91 BeanDefinition bd = registry.getBeanDefinition(candidateName);
92 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
93 !alreadyParsedClasses.contains(bd.getBeanClassName())) {
94 candidates.add(new BeanDefinitionHolder(bd, candidateName));
95 }
96 }
97 }
98 candidateNames = newCandidateNames;
99 }
100 }
101 while (!candidates.isEmpty());
102
103 // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
104 if (sbr != null) {
105 if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
106 sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
107 }
108 }
109
110 if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
111 ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
112 }
113 }
114
115 }
Spring IOC:BeanDefinition加载注册流程(转)的更多相关文章
- spring(四):IoC初始化流程&BeanDefinition加载注册
ApplicationContext context = new ClassPathXmlApplicationContext("hello.xml"); /** * * @par ...
- Spring IOC bean加载过程
首先我们不要在学习Spring的开始产生畏难情绪.Spring没有臆想的那么高深,相反,它帮我们再项目开发中制定项目框架,简化项目开发.它的主要功能是将项目开发中繁琐的过程流程化,模式化,使用户仅在固 ...
- Spring源码:Spring IoC容器加载过程(2)
Spring源码版本:4.3.23.RELEASE 一.加载XML配置 通过XML配置创建Spring,创建入口是使用org.springframework.context.support.Class ...
- Spring源码:Spring IoC容器加载过程(1)
Spring源码版本:4.3.23.RELEASE 一.加载过程概览 Spring容器加载过程可以在org.springframework.context.support.AbstractApplic ...
- 梳理源码:spring ioc容器加载的流程图
- 【死磕 Spring】—— IoC 之加载 BeanDefinition
本文主要基于 Spring 5.0.6.RELEASE 摘要: 原创出处 http://cmsblogs.com/?p=2658 「小明哥」,谢谢! 作为「小明哥」的忠实读者,「老艿艿」略作修改,记录 ...
- Spring源码之IOC容器创建、BeanDefinition加载和注册和IOC容器依赖注入
总结 在SpringApplication#createApplicationContext()执行时创建IOC容器,默认DefaultListableBeanFactory 在AbstractApp ...
- Spring源码加载BeanDefinition过程
本文主要讲解Spring加载xml配置文件的方式,跟踪加载BeanDefinition的全过程. 源码分析 源码的入口 ClassPathXmlApplicationContext构造函数 new C ...
- 【死磕 Spring】----- IOC 之 加载 Bean
原文出自:http://cmsblogs.com 先看一段熟悉的代码: ClassPathResource resource = new ClassPathResource("bean.xm ...
随机推荐
- 洛谷P1125——笨小猴(简易模拟)
https://www.luogu.org/problem/show?pid=1125 题目描述 笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼.但是他找到了一种方法,经试验证明,用这种方法去 ...
- Django学习day02随堂笔记
每日测验 """ 今日考题 1.谈谈你对web框架的认识,简述web框架请求流程 2.python三大主流web框架的区别 3.安装django需要注意的事项有哪些(最少 ...
- scrapy各种持久化存储的奇淫技巧
理论 磁盘文件: 基于终端指令 1)保证parse方法返回一个可迭代类型的对象(存储解析到的页面内容) 2)使用终端指令完成数据存储到指定磁盘文件中的操作,如:scrapy crawl 爬虫文件名称 ...
- 在PHP中检测一个类是否可以被foreach遍历
在PHP中,我们可以非常简单的判断一个变量是什么类型,也可以非常方便的确定一个数组的长度从而决定这个数组是否可以遍历.那么类呢?我们要如何知道这个类是否可以通过 foreach 来进行遍历呢?其实,P ...
- Jmeter扩展组件开发(7) - 自定义java请求的开发
CODE package com.demo;import org.apache.jmeter.config.Arguments;import org.apache.jmeter.protocol.ja ...
- Django边学边记—新手Django建项目各流程细节
一.准备虚拟环境 1)安装 virtualenv pip install virtualenv 2)virtualenvwrapper pip install virtualenvwrapper-wi ...
- Oracle Haip无法启动问题学习
一.目标:Oracle Haip 启动报错 需求:日常运维过程中,已经遇到两次由于HAIP引发的问题,特此进行记录. 本次问题是看着大佬-李海清操作,整完了记录一下,上一次HAIP折腾了4个小时. O ...
- 图神经网络(GNN)--slide
课件是学习小组汇报时用的,许多资料是从大佬哪里搬运的.Tex文档也在里面. GNN课件,下载不了,可以点击 带你入门图神经网络(GNN) 图神经网络(GNN)学习推荐网址 傅里叶分析之掐死教程(完整版 ...
- HttpRunner3.X - 全面讲解如何落地项目实战
一.前言 接触httprunner框架有一段时间了,也一直探索如何更好的落地到项目上,本篇主要讲述如何应用到实际的项目中,达到提升测试效率的目的. 1.项目难题 这个月开始忙起来了,接了个大项目,苦不 ...
- The art of multipropcessor programming 读书笔记-硬件基础2
本系列是 The art of multipropcessor programming 的读书笔记,在原版图书的基础上,结合 OpenJDK 11 以上的版本的代码进行理解和实现.并根据个人的查资料以 ...