程序入口:
接着上一篇博客中看完了在AnnotationConfigApplicationContext
的构造函数中的register(annotatedClasses);
将我们传递进来的主配置类添加进了BeanFactory
, 本片博客继续跟进refresh();
看看Spring如何继续初始化Spring的环境
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses)
refresh();
}
跟进refresh()
, 源码如下: 主要做了如下几件工作
- 刷新的预准备
- 比如: 设置时间的锚点,加载上下文环境变量
- 获取BeanFactory
- 执行所有的
BeanFactoryPostProcessor
- 执行所有的
BeanPostProcessor
- ...
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//准备刷新
prepareRefresh();
//获取BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备BeanFactory
prepareBeanFactory(beanFactory);
try {
// 方法中没有任何实现的逻辑
postProcessBeanFactory(beanFactory);
// invoke BeanFactoryPostprocessor, 执行bean工厂的后置处理器
//如果我们没有手动往Spring中注入bean工厂的后置处理器,那么此时仅有一个,也是beanFactoryMap中的第一个RootBeanDefinition-> ConfigurationClassPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 bean的后置处理器, 这些处理器可以在bean的构造方法执行之后再执行init()方法前后执行指定的逻辑
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
}
}
刷新的准备工作
这个方法没啥可看的重要逻辑,记录了下开始的时间,然后为Spring的上下文加载可用的环境变量
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// 这个是protected类型的方法,目前还没有任何实现
initPropertySources();
// 校验所有需要的properties是否都被解析过了
// getEnvironment() 得到系统环境, 后续的@Profile使用
getEnvironment().validateRequiredProperties();
this.earlyApplicationEvents = new LinkedHashSet<>();
}
获取BeanFactory
获取出BeanFactory
,接下来的工作重点是去扫描出程序员提供的类,然后将它们放进BeanFactoryMap
中,在此过程中穿插执行BeanFactoryPostProcessor
和BeanPostPorcessor
, 不难看出后续工作的进展都离不开这个BeanFactoryMap
,这个map在哪里呢? 就在我们的beanFactory
中,因此在刷新的最开始,获取出bean工厂
当前类是AbstractApplicationContext
,上图是它的继承类图,通过上图可以看到,它是入口AnnotationConfigApplicationContext
和GenericApplicationContext
的父类,而Spring的BeanFactory
是在GenericApplicationContext
中实例化的,故, 获取beanFactory
的逻辑肯定在当前方法中被设计成抽象的方法,而由自己具体实现,源码如下:
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
准备beanFactory
上面的逻辑是获取出BeanFactory
, 那什么是准备BeanFactory
呢? 看它的注解解释是: 为BeanFactory
配置上它应该具有的所有特征, 那BeanFactory应该有什么特征呢? 类加载器 , bean表达式的解析器 , property与对象的转换器 , bean的后置处理器 , 添加禁止用户注入的bean的信息 , 注入bean的替换 , 添加默认的和环境相关的bean
源码如下:它的解析我们写在下面
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 添加类加载器
beanFactory.setBeanClassLoader(getClassLoader());
// 设置bean标签的解析器, 一般我们使用spel标签比较多,但是Spring也有自己的Bean标签
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
// </property ref="XXX"> 解析转换xxx 替换成对象
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
// 添加bean的后置处理器,很显然这里添加的是Spring自己的后置处理器
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
// 当用户企图注入下面类型的对象时, 会被Spring忽略
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
//用户穿进来的是 BeanFactory.class , 那Spring会将她替换成beanFactory
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
// Register early post-processor for detecting inner beans as ApplicationListeners.
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
// Detect a LoadTimeWeaver and prepare for weaving, if found.
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
// 意思是如果自定义的Bean中没有名为"systemProperties"和"systemEnvironment"的Bean,
// 则注册两个Bena,Key为"systemProperties"和"systemEnvironment",Value为Map,
// 这两个Bean就是一些系统配置和系统环境信息
// 注册默认的和环境相关的 bean Sping会检测,我们自己注册进来的Bean中有没有下面的名字叫下面三个串的对象, 没有的话就帮我们注入进来
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) { // environment_bean_name
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {// system_properties_bean_name
beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {// system_environment_bean_name
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}
如上代码的作用就是为BeanFactory
初始化了Spring规定的几个必须的配置属性,其中比较值得注意的地方就是它添加的BeanPostProcessor
, 虽然这是Spring原生的bean的后置处理器,但是也是第一次出现,很有意义,配置expressContext等工作, 这个后置处理器会在Bean的构造的构造过程中,动态的拦截插手
此外,添加了忽略注入的对象,当程序员向注入Spring启动时,依赖的原生对象时,会被忽略注入,企图注入BeanFactory,资源解析器,事件发布器,应用上下文时,被Spring使用原生的对象替换掉
执行所有的BeanFactory的后置处理器
执行BeanFactory的后置处理器,具体是哪些呢? 其实是两部分,一部分是用户自己添加的,另一部分是Spring在启动过程中自己添加进去的
先说用户自己添加的情况,不知道大家有没有发现,源码读到这里其实还没看到Spring进行包扫描,既然没有进行包扫描那程序员通过@Compoennt
注解添加进去的 bean工厂的后置处理器 就还没有被Spring所识别到,没错,这里能被识别到的 BeanFactoryPostProcessor
是程序员通过context.addBeanFactoryPostProcessor(new MyBeanFactoryPostProcessor());
添加进来的
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
继续跟进invokeBeanFactoryPostProcessors()
方法,源码如下,这个方法在执行BeanFactoryPostProcessor
不得不说他真的很重要,很长
它的主要逻辑是: 开始是一个if
分支判断beanFactory
的合法性,上篇博文中我们看到了,所谓的beanFactory其实本身也实现了注册器接口,有注册bean的功能, 于是他将BeanFactory
强转成了注册器类型
紧接着Spring定义了两个新的List, 一个叫regularPostProcessors
一个叫registryProcessors
, 前者用来存放程序员自己添加进来的BeanFactoryPostProcessor
, 后者用来存放程序员自己添加进来的BeanDefinitionRegistryPostProcessor
, 为什么使用两个集合呢? 参见下图:
通过上面的图可以看到,BeanFactoryPostProcessor
是*的接口,BeanDefinitionRegistryPostProcessor
是继承了*接口然后自己做出了拓展, 一般程序员通过BeanFactoryPostProcessor
在bean的构造方法之前进行插手的话,最常用的就是选择直接自己实现BeanFactoryPostProcessor
,虽然实现BeanDefinitionRegistryPostProcessor
也行
BeanDefinitionRegistryPostProcessor
对BeanFactoryPostProcessor
做出来拓展, Spring需要保证拓展的方法被执行到,重写的父类的方法也要被执行到,因此选择使用两个集合,循环所有的bean工厂的后置处理器,按照不同的分类分别对待
分好类之后,又创建了一个list叫currentRegistryProcessors
这个List中存放的是 Spring自己的提供的BeanFactoryPostProcessor
的实现, 其实这个实现在前面提到过好多次了. 他就是ConfigurationClassPostProcessor
现在一共是三个集合,其中两个集合中的存放的对象是一样的,于是Spring将其实两个存放BeanDefinitionRegistryPostProcessor
的集合进行了合并
接下来就是真正的开始执行的工作
- 执行
BeanDefinitionRegistryPostProcessor
- 执行
BeanFactoryPostProcessor
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set<String> processedBeans = new HashSet<>();
// 通过看BanFactory的继承体系,能看到它实现了 BeanDefinitionRegistry接口
if (beanFactory instanceof BeanDefinitionRegistry) {
// 将工厂强转成 注册器
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
// 存放程序员添加进来的 BeanFactoryPostProcessor
List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
// 存放程序员添加进来的 BeanDefinitionRegistryPostProcessor
List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
System.out.println(beanFactoryPostProcessors.size());
// 自定义的beanFactoryPostProcessors
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
registryProcessor.postProcessBeanDefinitionRegistry(registry);
registryProcessors.add(registryProcessor);
}
else {//BeanDefinitionRegistryPostProcessor BeanfactoryPostProcessor
// 将自定义的BeanFactoryPostPorcessor 添加到了 上面的 ArrayList中regularPostProcessors
regularPostProcessors.add(postProcessor);
}
}
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
boolean reiterate = true;
while (reiterate) {
reiterate = false;
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (!processedBeans.contains(ppName)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
reiterate = true;
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
}
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
...
}
接下来与其说 看一下它如何执行BeanDefinitionRegistryPostProcessor
, 到不如说 看一下如何执行ConfigrationClassPostProcessor
对BeanFactoryPostProcessor
的拓展方法
postProcessor.postProcessBeanDefinitionRegistry(registry);
继续跟进,经过两个没有啥重要逻辑的方法之后,进入到下面的逻辑中, 有来了一个高潮,在这个方法中完成了包扫描工作
一开始获取出beanFactoryMap
中的全部的BeanDefinitionName
, 这时一共有几个? 其实是7个, 其中6个是在创建AnnotatedBeanDefinitionReader
时添加进去的6个, 另外的哪一个就是在前面的register()
方法中,注册的我们的主配置类MainConfig
紧接着是一个循环判断语句,循环这七个BeanDefinition
, 目的有两个,第一个把我们自己的MainConfig
配置类找出来放到下面的configCandidates
集合中,因为这是个配置类啊,上面会有@ComponentScan(value="XXX")
通过这个注解提供的包信息,Spring就能进一步进行包扫描,找到用户提供的所有的类信息,将它们加载进容器中, 第二个判断一下我们的MainConfig
类上有没有添加@Configuration
如果存在这个注解标记它为full,Spring就认为我们的当前的运行的上下文环境是全注解环境,并且会为MainConfig
生成一个cglib代理对象,进一步保证了Spring的单例特征,如果没有这个注解,但是存在@Component @ComponentScan @Import @ImportResource
Spring标记它为lite.认为当前的上下文环境为非全注解模式
怎么理解这个全注解与非全注解呢? 字面意思也是,全注解就是不存在配置文件, 不存在配置文件的话,程序员不可能不提供@ComponentScan
让Spring去扫描完成Bean的注入,同时程序员也会提供一个@Configuration
明确的标识这是一个配置类, 那非全注解呢, 就是可能存在注解和XML共存的现象, Spring这时也会同时支持注解+xml的读取
接着又是排序,创建名称生成器
紧接着创建了一个ConfigurationClassParser
配置类的解析器,这个解析器,见名知意,用来解析配置类, 现在谁是配置类呢? 其实就是在上面的循环中唯一被添加进list的,我们提供的MainConfig
, 因为我在他身上添加了@Configuration
注解
下面的主要逻辑是解析配置类,我把解释写在如下代码的下面
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 定义一个List 存放项目中添加了 @Compennt注解的类
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// 获取容器中注册的所有bd名字
// 一共 7个 , 6个rootBeanDefinition 1个我们自己的MainConfig
// 获取出一开始我们Spring自己添加的6个Processor, 和我们添加的MainConfig
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
//
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 他是怎么判断出来的呢? 在上面, 如果判断得出当前的类添加了@Configration , 就给他标记 full, 在上面的if分支语句中,添加了full的类,不会添加进 configCandidates 中,故为空
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
// 现在我们使用的BeanDefinitionRegistry是其实是Spring的Bean工厂(DefaultListableBeanFactory) 他是SingletonBeanRegistry的子类的话
if (registry instanceof SingletonBeanRegistry) {
// 将registry强转为SingletonBeanRegistry
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
// 是否有自定义的
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
//SingletonBeanRegistry中有id为 org.springframework.context.annotation.internalConfigurationBeanNameGenerator
// 如果有则利用他的,否则是spring默认的
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// 这是个配置类的解析器 会解析每一个添加了 @Configuration 的类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 仅仅处理添加了@Configuration注解的类, 进行包扫描,跟进去
parser.parse(candidates);
// 运行到这里完成了扫描,BeanFactory中的BeanDefinitionMap中就多了我们字节添加进去的bean信息
parser.validate();
//map.keyset
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
...
跟进这个 parser.parse(candidates);
,这里就来到了又一波高潮,准备开始包扫描了
不怕麻烦,再提一下,当前的这个对象就是我们的MianConfig
,它是被AnnotatedBeanDefinitionReader
读取到的,所以它一定是AnnotatedBeanDefinition
, 所以一定会进入到第一个if分支中
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<>();
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
继续跟进,同样是经过了几个没有重要逻辑的方法之后,进入到下面的方法中,看他是如何下面这个重要的方法中
这个方法主要做了两件大事:
- 处理扫描添加有
@Component
注解的普通类,并将它们直接添加到BeanFactoryMap
中 - 扫描处理添加有
@Import
注解
看他首先取出所有的@CompoenntScan
注解,循环遍历注解,每次循环都使用this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
进行真正的包扫描
跟进这个方法中,可以看到它一开始就自己重新new 了一个包扫描器,然后解析当前循环的@ComponentScan
注解上的其他如excludeFilters,includeFilters
等属性,最后开始真正的进行包扫描, 在这个扫描的过程中,会将命中符合条件的普通类(如被@Component
标识),进行如下处理
- 设置scope信息
- 生成BeanName
- 给扫描出来的这些添加上默认的属性信息比如默认全是
Lazy
- 进一步,处理这些类上的注解信息,比如
@Lazy , @Primary , @DependsOn , @Role , @Description
,用这些信息覆盖默认的信息 - 将扫描出来的普通类直接添加到BeanFactoryMap中
完成了上面的普通类的扫描工作之后,下一个高潮就来了,处理@Import()的三中情况,它的解析我写在如下代码的下面
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// Recursively process any member (nested) classes first
// 递归地首先处理任何成员(嵌套)类
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// 处理所有的@ComponentScan注解, 也就是读取到了我们在MainConfig中使用@ConponentScans 中添加的元信息, 如value=com.changwu
// basePackages lazyInit userDefualtFileter ,,, includeFileters excludeFilters scopeResolver nameGenerate ,,,
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
// 为什么要循环,以为 @ComponentScans(value={1,2,3}) value是一个数组
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// 扫描com.changwu下面的普通类, 也就是添加了@Component注解的类, 然后将扫描出来的bean放到map中
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
//检查扫描出来的类当中是否还有configuration
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
//检查 看看被扫描的普通类有没有添加 配置相关的注解
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
AnnotationAttributes importResource =
...
跟进上面的 processImports(configClass, sourceClass, getImports(sourceClass), true);
方法,看他如何处理@Import
注解,通过下面的代码不难看出@Import
注解存在三种情况,分别是
- ImportSelector
- ImportBeanDefinitionRegistrar
- 普通类
第一种情况处理ImportSelector
, 这个ImportSelector
是很好用的组件,首先第一点: 我们可以通过自动ImportSelector
完成类的批量注入,但是吧这个功能感觉就像是鸡肋,弃之可惜,食之无味, 其实他还有一个妙用!配合jdk的动态代理我们可以实现类似AOP的切面,针对某一个对象进行动态的代理, 举个例子: 自定义一个类,实现BeanPostProcessor
接口,然后重写它的public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { ... }
然后根据用户名进行判断,当找我们指定的用户时,我们可以使用JDK的动态代理完成将这个对象转换成代理对象,进而实现切面的增强
看一下它的处理,它判断出@Import
中含有ImportSelector.class
时,就通过反射将这个对象创建出来代理对象,狸猫换太子,把代理对象交给Spring,得到ImportSelector
的对象,具体反射出来的对象的实例就是程序员自定义的那个ImportSelector
,得到这个对象之后,然后调用它的selectImports()
方法,就返回了程序员指定的想批量导入DaoIOC中的对象的全类名, 下一步就是将这些类注入到IOC中,Spring的做法是递归调用, 因为上面说了,当前方法可以实现的三种Bean的注入,一般来说,通过 ImportSelecor
导入的类就是普通类了, 会进入下面代码中的最后一个else语句块
第二种情况,处理ImportBeanDefinitionRegistrar
,Spring的做法是,将它添加进一个map中
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
第三种情况,同样和第一种情况是一样的,也是先将信息放到map中
this.configurationClasses.put(configClass, configClass);
源码如下:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
// 如果没有添加了@Implot注解的类,直接退出去
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
// 情况1: 处理 @ImportSelector 注解
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
// 被循环获取出现在Spring自己的以及扫描出来的全部的对象的Class描述
Class<?> candidateClass = candidate.loadClass();
// 只要这个对象的@Import(value= ImportSelector.clas)就被命中
// 反射实现一个对象,反射创建的这个对象就是我们的手动添加的 继承 ImportSelector 的那个对象
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
} else {
// 回调反射出来的这个对象啊的 SelectImports() 方法,就能动态的获取出我们手动添加进去的,准备批量注入的 对象的 ClassName 数组
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
// 将importClassNames添加进一个list -- annotatedClasses中,然后返回出来
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归调用, processImports()方法,显然,再次递归的话,传递进去的importSourceClasses就是当前的类, 如果当前类是普通类,递归时就不再来到这里了, 而是进入下面的else代码块
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
} // 情况2: 处理@ImportBeanDefinitionRegistrar
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
// 没有和上面一样进行回调,而是放入到一个list中
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// 情况3: 处理普通类
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
// 否则,加入到importStack后调用 processConfigurationClass 进行处理
//processConfigurationClass() 方法就在下面, 里面主要就是把类放到configurationClasses
//configurationClasses是一个集合,会在后面拿出来解析成bd继而注册
//可以看到普通类在扫描出来的时候就被注册了
//如果是importSelector,会先放到configurationClasses后面进行出来注册
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
当处理完成了三种@Import
类型的导入方式之后,我们继续往下看,三种方式都是忙着把读取读来的信息往map中放,那么在哪里进行处理的呢? 思考一下,接下来是不是得将读取到的信息注册进IOC中? 没错,我们退会到ConfigurationClassPostProcessor
中的this.reader.loadBeanDefinitions(configClasses);
方法中
同样经过几个没有重要逻辑的方法之后,我们来到了ConfigurationClassBeanDefinitionReader
中,着重它的loadBeanDefinitionsForConfigurationClass()
方法, 源码我贴在下面:
看看他做了什么, 一目了然,很清晰的思路,很牛逼很牛逼!!!
如果Spring发现,当前的类是被导入进来的,他按照Bean导入进来的方式进行注册Bean,如果进给看一下,就能看熟悉的一幕,Spring使用Register
进行Bean的注册
如果Spring发现它有BeanMethod,也就是发现这个对象存在方法,换句话说发现我们的对象存在方法,就会进一步解析我们的方法,怎么解析方法呢? 按照对象的方法和类的方法分别解析,这也是为什么,当我们在配置类的静态方法中使用@Bean进行注入对象,即使已经为MainConfig生成了代理,依然会出现重复注入对象的情况,但是BeanName不一样哦,如果是静态方法+@Bean, BeanName是当前的方法名
接下来的逻辑是 解析XML与处理Registrar
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// 如果一个类是被 Import 进来的, 会在Spring进行标记,然后再这里完成注册
// @Import(aaa.class) 那这个aaa就是被Import的,在这里完成注册
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// xml
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 处理注册Registrar 的逻辑
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
代码读到这里其实已经深入很多层了,重要的逻辑也都过了一下,现在的工作就是层层的往回出栈,回到开始的PostProcessorRegistationDelegate
中的invokeBeanFactoryPostProcessors()
方法
上面的大部分篇幅都是当前方法中的 invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
方法层层递归进去的,现在我们从它开始往后看
刚开始不是说,一共是创建三个list吗?然后又把其中的两个进行了合并了, 那么接下来的工作就是去执行这两个list中剩下的没执行的逻辑, 没执行的就是,Spring自己提供的和程序员添加的BeanFactoryPostProcessor
的实现,没错就是执行重写的BeanFactoryPostProcessor()
的postProcessBeanFactory()
方法
// registryProcessors 其实就是唯一的 ConfigurationClassPostProcessor
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
// 自定义BeanFactoryPostProcessor 的 postProcessorBeanFactory()方法
invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
大家看,上面的两个方法是一样,只不过是传递进去的参数不一样而已,其实吧,高潮来了,如果大家还记得的话,应该猜到了现在的入参位置上的参数, 没错就是 ConfigurationClassPostProcessor
这个类, 它太牛逼了! 同时实现了BeanDefinitionRegistryPostProcessor
和BeanFactoryPostProcessor
的抽象方法, 下面就去具体看一下它的实现,准备好了吗? 来高潮了哦
源码如下: 它的解析我写在下面
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);
if (!this.registriesPostProcessed.contains(factoryId)) {
// BeanDefinitionRegistryPostProcessor hook apparently not supported...
// Simply call processConfigurationClasses lazily at this point then.
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
enhanceConfigurationClasses(beanFactory);
这是在干什么? 跟大家说,这太牛了!!!为什么说它牛? 不买关子,它在这里开启了JDK的动态代理
在这个方法中有一段判断逻辑,如下: 这是很赞的一段代码,感觉到了心跳的加速! 它判断当前的这个BeanDefinition
是不是full类型的, 关于这个Full的解释,其实我们上面的描述中有说过,就是说,如果我们的MainConfig
添加了@Configuration
注解,它就被会标记为FUll, 被标记为full的话,就会在下面的代码中产生cglib的动态代理,也就是说,我们获取到的存入容器的MainConfig
可以不是普通的原始对象, 而是被Cglib增强后的对象, 这有什么用呢? 用处可大了! 我们通常会在配置类中添加@Bean注解,注入对象,但是如果被添加了@Bean注解的方法彼此之间相互调用的户,就会出现重复注入的现象,Spring通过下面的判断,进行代理,不再给用户原始的Mainconfig
,这样就实现对方法调用的控制,进而保证了百分百单例的情况
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
// 判断isFull, 看看是不是添加了@Configuration的全注解的类
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
if (!(beanDef instanceof AbstractBeanDefinition)) {
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
}
else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
"' since its singleton instance has been created too early. The typical cause " +
"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
"return type: Consider declaring such methods as 'static'.");
}
// 如果是的话,放到这个linkedHashMap中
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
接着往下看代码就可以看到Spring底层使用原生cglib进行代理的逻辑了,
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(configSuperClass);
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
这个过程中有几个需要注意的地方,一般我们自己实现Cglib时, 都只是设置一个setSuperclass(XXX)
然后对这个XXX进行增强,但是Spring没这么简单,它还enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
想想,为什么还要这个接口呢?看下面的图
通过上面的图可以看到,这个接口实现了beanFactoryAware
,而这个beanFactory
中存在setBeanFactory(BeanFactory bf)
怎么样? 有思路没?
整理一下思路,就是说,Spring的目的就是将程序员传递进来的MainConfig
进行动态代理,为啥要代理呢? 因为有的程序员会故意搞破坏,会使用被@Bean
标注的方法之间相互调用,导致Bean的多次注入,于是Spring想通过代理,返回给用户一个代理对象,然后添加动态的判断, 如果容器中已经存在bean了,就从容器中获取,不再重复注入,没有的话就注入进去一个
这就引出了为什么,代理对象需要一个BeanFactory
,因为BeanDefinition
都在BeanFactory中,这也是为什么上面需要setInterface()
接着enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
设置一个生成策略, 因为我们生曾的代理类需要一个BeanFactory类型的变量啊,没有这个引用,如何接受前面set的BeanFactory???
再往后的亮点就是enhancer.setCallbackFilter(CALLBACK_FILTER);
设置回调的拦截器,看看有哪些拦截器呢? 代码如下:
private static final Callback[] CALLBACKS = new Callback[] {
// 第一个实现, 增强方法, 主要控制bean的作用域换句话说就是让每一次调用方法不再去new, 跟进去看看
new BeanMethodInterceptor(), // 他是当前类的内部类
//设置一个beanFactory
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
我们跟进去BeanMethodInterceptor
,这个类也很精彩,玩过cglib的人都知道需要一个inteceptor,而我们正在看的这个接口就实现了methodInterceptor
,重不重要,你懂的...
直接看它的intercept()
方法, 细细品味这个方法,很有味道哦!!!, 它的解析我写在这个方法的下面
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
// enhancedConfigInstance 是代理对象
// 通过代理对象enhancedConfigInstance中cglib生成的成员变量$$beanFactory获得beanFactory。
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// Determine whether this bean is a scoped-proxy
Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}
if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
factoryContainsBean(beanFactory, beanName)) {
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
if (factoryBean instanceof ScopedProxyFactoryBean) {
// Scoped proxy factory beans are a special case and should not be further proxied
}
else {
return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
}
}
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// The factory is calling the bean method in order to instantiate and register the bean
// (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
// create the bean instance.
if (logger.isWarnEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
logger.warn(String.format("@Bean method %s.%s is non-static and returns an object " +
"assignable to Spring's BeanFactoryPostProcessor interface. This will " +
"result in a failure to process annotations such as @Autowired, " +
"@Resource and @PostConstruct within the method's declaring " +
"@Configuration class. Add the 'static' modifier to this method to avoid " +
"these container lifecycle issues; see @Bean javadoc for complete details.",
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
}
// 满足条件 调用父类的构造方法new 对象
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
首先,它从代理对象中获取出beanFactory
然后他处理 FactoryBean
的情况,这个FactoryBean
太牛了,因为当程序员把一个FactoryBean
注入到IOC时,附带的还会把另一个对象驻入进IOC, 它是如何进行区分判断的呢? Spring会使用一个BeanFactory.FACTORY_BEAN_PREFIX == &
这个前缀去匹配, 比如userDao3()中调用了userDao4(), 他就是用&userDao4当成key去beanFactory中获取,如果获取获取出对象了,说明这是个FactoryBean
需要对获取出来的这个对象进一步生成代理
接下来判断,是new 呢? 还是从Factory中获取呢?
Spring的判断依据是根据方法名,判断调用方法和正在执行的方法是同一个方法,根据什么呢? 只要名字相同, 结论就是直接new
举个例子:
userDao3(){}
// 它的调用方法和正在执行的方法是同一个方法,怎么相同呢? 名字相同, 结论就是直接new
userDao4(){
userDao3()
}
userDao4()是调用方法, userDao3()执行方法 userDao4 和 userDao3 名字不一样, 所以选择getBean()
第二个例子:
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("intercept.............");
methodProxy.invokeSuper(o,objects);
return null;
}
比如我们仅仅执行代理方法A, 这个A方法 就时上面的method 也是methodProxy, 但是如果我们在代理方法A中执行B方法, 这时 A == method != methodProxy == B
代码看到这里,其实一开始的refresh()
中的invokeBeanFactoryPostProcessors(beanFactory);
方法就看完了, 着呢么样刺激不?
有错误的话欢迎批评指出,有过对您有帮助,欢迎点赞支持
Spring 源码阅读 二的更多相关文章
-
Bean实例化(Spring源码阅读)-我们到底能走多远系列(33)
我们到底能走多远系列(33) 扯淡: 各位: 命运就算颠沛流离 命运就算曲折离奇 命运就算恐吓着你做人没趣味 别流泪 心酸 更不应舍弃 ... 主题: Spring源码阅读还在继 ...
-
搭建 Spring 源码阅读环境
前言 有一个Spring源码阅读环境是学习Spring的基础.笔者借鉴了网上很多搭建环境的方法,也尝试了很多,接下来总结两种个人认为比较简便实用的方法.读者可根据自己的需要自行选择. 方法一:搭建基础 ...
-
初始化IoC容器(Spring源码阅读)
初始化IoC容器(Spring源码阅读) 我们到底能走多远系列(31) 扯淡: 有个问题一直想问:各位你们的工资剩下来会怎么处理?已婚的,我知道工资永远都是不够的.未婚的你们,你们是怎么分配工资的? ...
-
Spring源码阅读-ApplicationContext体系结构分析
目录 继承层次图概览 ConfigurableApplicationContext分析 AbstractApplicationContext GenericApplicationContext Gen ...
-
Sping学习笔记(一)----Spring源码阅读环境的搭建
idea搭建spring源码阅读环境 安装gradle Github下载Spring源码 新建学习spring源码的项目 idea搭建spring源码阅读环境 安装gradle 在官网中下载gradl ...
-
Spring源码阅读总结(Ing)
一.Spring源码架构 Spring源码地址 二.Spring中的设计模式 1.工厂模式 BeanFactory 2.模板模式 模板的使用者只需设计一个具体的类,集成模板类,然后定制那些具体方法,这 ...
-
Spring源码阅读笔记02:IOC基本概念
上篇文章中我们介绍了准备Spring源码阅读环境的两种姿势,接下来,我们就要开始探寻这个著名框架背后的原理.Spring提供的最基本最底层的功能是bean容器,这其实是对IoC思想的应用,在学习Spr ...
-
Spring源码阅读 之 配置的读取,解析
在上文中我们已经知道了Spring如何从我们给定的位置加载到配置文件,并将文件包装成一个Resource对象.这篇文章我们将要探讨的就是,如何从这个Resouce对象中加载到我们的容器?加载到容器后又 ...
-
spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
随机推荐
-
swift someObject == nil 如何实现
以前的编程经验告诉我们判断一个对象是否为空或者是否实例化可以这样 if(someObject == nil){ //do something }else{ } 但是在Swift中这样是不行的,然后我在 ...
-
类名.class与类名.this详解
类名.class 我们知道在java中,一个类在被加载的时候虚拟机就会自动的生成一个这个类的一个Class类型的“类对象”,每个类都对应着一个这样的类对象,通过这个Class类型的类对象,我 ...
-
dubbo简述
转http://blog.csdn.net/hzzhoushaoyu/article/details/4327309 一.前言 部门去年年中开始各种改造,第一步是模块服务化,这边初选dubbo试用在一 ...
-
Example: Develop Web application on Baidu App Engine using CherryPy
In the past few months, I have developed two simple applications on Baidu App Engine. Compared to Go ...
-
PAAS平台7&;#215;24小时可用性应用设计
如今非常多企业都在搭建自己的私有PAAS平台,当然也有非常多大型互联网公司搭建共同拥有PAAS平台(比如SAE/BAE/JAE(jae.jd.com)).那么使用PAAS平台来部署SAAS应用有哪些优 ...
-
深入理解ajax系列第四篇——FormData
前面的话 现代Web应用中频繁使用的一项功能就是表单数据的序列化,XMLHttpRequest 2级为此定义了FormData类型.FormData为序列化表单以及创建与表单格式相同的数据提供了便利. ...
-
关于WIN7 内存占用很大的 问题svchost.exe
svchost.exe 是用来启动系统服务的,所以某个 svchost.exe 占用内存过大,可能就是它启动的那个服务占用内存过大,所以只要停止并禁用那个服务就行了. 一般来说占用内存最大的服务是 S ...
-
Win10开启“上帝模式”
win10的上帝模式就是win10的全部功能展示模式,因为功能太强大,所以被戏称为"上帝模式".要开启win10的上帝模式,需要按下面的步骤来操作:1.在window桌面新建一个普 ...
-
Linux目录结构和基础知识
目录结构: /bin:存放系统常用的命令程序 /boot:系统启动或引导所需要的一些文件 /dev:可用的设备文件 /etc:系统配置相关的东西 /home:所有用户的主目录 /lib,lib64:存 ...
-
Windows下安装lxml库方法
如果直接用pip install lxml安装成功,那么恭喜!!! 一般在windows安装都十分蛋疼,pip无法直接安装(提示错误一大片,此处省略……) 因此选择wheel安装方式,步骤如下: 1. ...