Spring处理@Configuration的分析

声明:本文若有任何纰漏、错误,还请不吝指出!

序言

@Configuration注解在SpringBoot中作用很大,且不说SpringBoot中的外部化配置,一些第三方组件也是通过这个注解完成整合的,常用的比如说mybatis,就是利用了@Configuration这个注解来实现的。

在注解类中,还可以使用@Bean的方式向Spring容器中,注入一些我们自定义的组件。

SpringBoot中各种Enable又是如何实现的?和@Configuration又有什么联系呢?

这就要了解Spring是怎么对待被@Configuration所注解的类。

环境

SpringBoot 2.2.6RELEASE

Spring 5.2.5.RELEASE

正文

注解依附于具体的Java类,所以如果想获取注解的信息,必须先将类加载进来,才能从Class对象获取到其注解元信息。

好在Spring容器启动之前,已经把所有需要加载的Bean,封装成一个BeanDefinition对象,最终注册到BeanDefinitionRegistry中。

BeanDefinition包含了一个Bean所有的信息,自然也包含了它的元注解信息。

有了这个就能轻而易举的获取到标注有@Configuration注解的BeanDefinition,从而去处理这个配置类拥有的各种配置信息。

有了BeanDefinition之后,下面一步就是要进行Bean的实例化了。如果一个Bean被实例化后,就没有可操作的机会了,因此SpringBean的实例化前预留了一些自定义的处理时机。

BeanFactoryPostProcessor就是这样的一个功能,用于在Bean实例化之前,做一些其他的处理操作。

对配置类的处理,也正是利用了这一预留点。

BeanDefinitionRegistryPostProcessor

处理配置类,第一步就要从茫茫的BeanDefinition中,找出哪些是配置类。

容器开始启动之前的一些准备动作,这里不说明,主要是扫描classpath,然后将生成BeanDefinition

直接从容器的启动开始简单下调用栈

Spring容器真正开始启动的是从这里开始的org.springframework.context.support.AbstractApplicationContext#refresh,在这个方法中,会去执行所有的BeanFactoryPostProcessor

通过一个委托类org.springframework.context.support.PostProcessorRegistrationDelegate,执行所有的BeanFactoryPostProcessor后置处理逻辑。

BeanFactoryPostProcessor有一个子接口是BeanDefinitionRegistryPostProcessor,这个接口的主要作用就是在其他后置处理执行之前,额外注册一些BeanDefinition进来。

想想在配置类中使用的@Import@Bean,就可以猜到,这些注解的处理就是由这个处理器进行处理的。

BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor是放到一起处理的,只不过BeanDefinitionRegistryPostProcessor的执行时机,早于BeanFactoryPostProcessor的执行时机。

// org.springframework.context.support.PostProcessorRegistrationDelegate
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) { Set<String> processedBeans = new HashSet<>();
// 如果BeanFactory同时又是一个BeanDefinitionRegistry的话
// 例如 DefaultListaleBeanFactory
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
// 如果有直接注册到Context的后置处理器,
// 先执行直接添加到ApplicationContext的BeanDefinitionRegistryPostProcessor处理器
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
// 执行BeanDefinitionRegistryPostProcessor处理器
registryProcessor.postProcessBeanDefinitionRegistry(registry);
// BeanDefinitionRegistryPostProcessor同时又是一个BeanFactoryPostProcessor
// 待所有的BeanDefinitionRegistryPostProcessor执行完后,再来执行它
registryProcessors.add(registryProcessor);
}
else {
// 加入到BeanFactoryPostProcessor处理器集合中,待所有的BeanDefinitionRegistryPostProcessor执行完后,来执行它
regularPostProcessors.add(postProcessor);
}
} List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
// 对从BeanDefinitionRegistry中的BeanDefinition做后置处理
// 先执行被@PriorityOrdered注解的BeanDefinitionRegistryPostProcessor
// 并且按排序大小进行优先级排序
// 根据类型,从BeanDefinitionRegistry中查找出所有的BeanDefinitionRegistryPostProcessor的是实现类,及子类
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
// 使用@PriorityOrdered注解的先查找出来
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
// 按编号大小排序,升序排列
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
// 执行BeanDefinitionRegistryPostProcessor
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear(); // 处理被注解@Ordered标注的BeanDefinitionRegistryPostProcessor,
// 并且按排序后排序后执行
// 根据类型,从BeanDefinitionRegistry中查找出所有的BeanDefinitionRegistryPostProcessor的是实现类,及子类
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
// 没被处理过且被注解@Ordered
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);
// 执行BeanDefinitionRegistryPostProcessor
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear(); // 再去执行其他的剩下的所有BeanDefinitionRegistryPostProcessor
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();
} // BeanDefinitionRegistryPostProcessor也是一个BeanFactoryPostProcessor
// 下面这部分就是执行postProcessBeanFactory方法,
// 会在@Configuration的proxyBeanMethods为true时对配置类做一个CGLIB增强,
// 表示对配置类中的BeanMethod创建时,使用代理创建
// 将增强后的类,替换到其BeanDefinition#setBeanClass
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
// 最后再执行直接注册到到ApplicationContext中的BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
} else {
// 处理直接通过ApplicationContext实例注册的BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
} // 上面就执行过了定义的所有的BeanDefinitionRegistryPostProcessor,以及实现的
// BeanFactoryPostProcessor#postProcessBeanFactory方法
// 接下来回去执行所有的BeanFactoryPostProcessor处理器 // 查找出所有注册的类型为BeanFactoryPostProcessor的BeanDefinition的name数组
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false); // 分别归类出使用@PriorityOrdered 和 @Ordered注解和没有使用的
List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> nonOrderedPostProcessorNames = new ArrayList<>();
for (String ppName : postProcessorNames) {
if (processedBeans.contains(ppName)) {
// 处理过的,不用重复处理
}
else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
} // 优先处理 PriorityOrdered.
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory); // 其次 Ordered.
List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
for (String postProcessorName : orderedPostProcessorNames) {
orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
sortPostProcessors(orderedPostProcessors, beanFactory);
invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory); //最后普通的 BeanFactoryPostProcessor
List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
for (String postProcessorName : nonOrderedPostProcessorNames) {
nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
beanFactory.clearMetadataCache();
}

上面这个方法执行完后,已经完成了所有BeanFactoryPostProcessor的执行,也自然已经处理过所有的配置类了。

ConfigurationClassPostProcessor

在众多的后置处理器中,有一个独属于@Configuration的后置处理器,就是ConfigurationClassPostProcessor,一个好的命名的效果,就体现出来了。

下面这个方法,负责两件事

  1. BeanDefinitionRegistry中筛选出配置类
  2. 对配置类的BeanDefinition进行解析
// org.springframework.context.annotation.ConfigurationClassPostProcessor
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 候选配置类集合
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// 获取所有的BeanDefinition的name数组
String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// 如果BeanDefinition中有这个属性存在,说明作为一个配置类已经被处理过了
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
// 检查是否为一个配置类
// 查看是否具有@Configuration注解
// 这里不会仅仅看BeanDefinition所代表的类直接标注的注解,而是会递归查找其注解的注解是否有为
// @Configuration,只要找到了那么当前的类就是一个配置类
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
} // 找不到就结束
if (configCandidates.isEmpty()) {
return;
} // 对使用了@Order的进行排序 自然排序也就是升序
// 注意不是@Ordered
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
}); // 如果有自定义Bean Name生成器,就使用自定义的
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
// 如果还没有初始化Environment对象,初始化一个
if (this.environment == null) {
this.environment = new StandardEnvironment();
} // 解析每一个被@Configuratin标注的注解类
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 {
parser.parse(candidates);
parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed); // 构造一个BeanDefinitionReader
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// 加载配置类中的@Bean,生成BeanDefinition
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses); candidates.clear();
// 下面这段主要是考虑到@Import进来的或者@ImportSource或者@Bean等方式注入进来的会有配置类
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty()); //把 ImportRegistry注册成一个Bean,以便支持 继承ImportAware 有注解类@Configuration的配置类
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
} }

这个方法执行完后,所有的配置类都会被进行处理,并且在此过程中,BeanDefinition的总量有可能会增加,有新的BeanDefinition在解析过程新增进来。

这些BeanDefinition的来源就是存在于配置类上的其他注解

ConfigurationClassParser

SpringBoot是如何使用一个@SpringBootApplication注解,完成了那么多的事情?

答案就在下面揭晓

// `org.springframework.context.annotation.ConfigurationClassParser
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 如果有Component注解
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// 首先递归处理内部类
processMemberClasses(configClass, sourceClass, filter);
} // 处理所有的@PropertySource注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
} // 处理所有的 @ComponentScan 和@ComponentScans
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 继续检查扫描的BeanDefinition有没有是配置类的
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());
}
}
}
} // 处理所有的@Import注解,将导入的Bean注册到BeanDefinitionRegistry
// 会递归查找出所有的@Import
// getImports会查找所有配置类上的@Import注解,@Import可以导入一个普通的配置类
// 也可以是ImportSelector或者ImportBeanDefinitionRegistrar的实现类
// 之所以需要ImportSelector和ImportBeanDefinitionRegistrar,是为了可以更加方便的一次注册多个类
// 这俩接口的实现类,也一要由Import导入进来,或者直接使用@Component,不过这样就就没必要去实现接口了
// 这三个的主要用途不是为了注册自己程序中的类成为一个Bean,而是为了那些不方便使用@Component注解的类
// @Component的更像一个为了业务代码使用的注解
processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // 处理所有的@ImportResource 注解,将导入的Bean注册到BeanDefinitionRegistry
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
} // 处理独立的 @Bean方法,生成BeanMethod
// 使用@Bean,方法要是可重写,也就是不能为default/private,因为要使用CGLIB代理
// 详细可进去下面方法细看
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
} // 处理接口的默认方法
processInterfaces(configClass, sourceClass); // 如果有父类,处理
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
} //没有父类,处理完成
return null;
}

看下@SpringBootApplication的定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration { } @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication { }

可以看到@SpringBootApplication在功能上也是一个@Configuration

这样就解释了,一般在SpringBoot的启动类上写了那么多注解,为啥可以被执行。

如果有看过各类@Enable注解,就一定会看到,每一个@Enable几乎都会被@Import所注解,而一般使用@Enable时,都会和@SpringBootApplication写一起,这个写法的一方面是比较清晰,集中写到一起,还有个原因就是部分@Enable在定义时,没有使用@Configuration来进行注解,需要借助于一个能被Spring容器启动时处理的配置类上。

上面的这段代码分析,正好解释了@Enable背后的实现原理。

总结

其实总的看下来,@Configuration就是一个标志注解,更大的作用就是为别的注解服务的。这么说有点矛盾,主要是觉得本身不具备什么功能性。

至于其能实现的对字段进行配置值绑定来说,可以使用@ConfigurationProperties或者@Value这两个注解来实现,由此可见,@Configuration并不是用于将配置文件的配置值,绑定到配置类的,这个工作和他没有任何关系,对于一些配置文件的配置来说,可以使用@Component注解来对普通的配置类注解,达到一样的效果,而并非一定要使用@Configuration(@Configuration注解派生自@Component)。

通过我们上面的分析,被@Configuration注解的类,仅有存在以上那几个注解时,才有意义,才能被ConfigurationClassPostProcessor所处理,而这个处理过程中,和配置值绑定一毛钱的关系都没有。

实际上配置值的绑定,都是在Bean实例化后,Bean属性填充期间进行的。

@ConfigurationProperties注解会在ConfigurationPropertiesBindingPostProcessor执行时进行处理,这个处理器是一个BeanPostProcessor

@Value注解的处理是在AutowiredAnnotationBeanPostProcessor这个BeanPostProcessor中来处理的,这个处理器同时也是处理@Inject@Autowired@ResourceBeanPostProcesoor

Spring或者SpringBoot中,大量的使用各种后置处理器,除了对主体框架(Bean的生命周期)的理解外,剩下的主要就是熟悉这些支持各种功能的PostProcessor

还有个值得注意的是,@Configuration有个方法proxyBeanMethods,这个方法返回true时,默认也是true,会对我们的配置类,生成一个代理类,注意,这里是直接生成一个代理类,并且最后实例化时,也是使用这个代理类进行实例化Bean,这个就给我们一个启发,如果想对一些无法直接修改又被Spring容器所管理的的Bean,是否可以通过自定义BeanDefinitionRegistryPostProcessor的方式,来对原Class做一个增强,从而实现我们的目的。

PS:是否具备切实可行性,并不保证,只是觉得如果遇到,可以尝试下。

Spring处理@Configuration的分析的更多相关文章

  1. Spring Security 源码分析(四):Spring Social实现微信社交登录

    社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...

  2. spring AOP源码分析(三)

    在上一篇文章 spring AOP源码分析(二)中,我们已经知道如何生成一个代理对象了,那么当代理对象调用代理方法时,增强行为也就是拦截器是如何发挥作用的呢?接下来我们将介绍JDK动态代理和cglib ...

  3. Spring Boot 入门详细分析

    推荐阅读: 我们为什么要学习 Spring Boot 我们搭建 Spring Boot 项目,可以使用 Spring 为我们提供的初始化网站,那个可能不太方便,今天呢,我们就来说说如何使用 IDEA ...

  4. Spring注解 @Configuration

    Spring注解 @Configuration 一.@Configuration的作用 二.@Configuration的Spring容器启动方式 三.不加@Configuration的@Bean的解 ...

  5. Spring IOC 源码分析

    Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文 ...

  6. Spring MVC源码分析(三):SpringMVC的HandlerMapping和HandlerAdapter的体系结构设计与实现

    概述在我的上一篇文章:Spring源码分析(三):DispatcherServlet的设计与实现中提到,DispatcherServlet在接收到客户端请求时,会遍历DispatcherServlet ...

  7. Spring IoC 源码分析 (基于注解) 之 包扫描

    在上篇文章Spring IoC 源码分析 (基于注解) 一我们分析到,我们通过AnnotationConfigApplicationContext类传入一个包路径启动Spring之后,会首先初始化包扫 ...

  8. Spring Security 源码分析 --- WebSecurity

    概述 spring security 源码分析系列文章. 源码分析 我们想一下,我们使用 ss 框架的步骤是怎么样的. @Configuration @EnableWebSecurity @Enabl ...

  9. 精尽Spring MVC源码分析 - 寻找遗失的 web.xml

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. Java 动态编译--DynamicCompiler

    java 动态编译自己写过程的机会比较少,记录一下: package com.xzlf.dynamicCompile; import java.io.IOException; import java. ...

  2. 挑战全网最幽默的Vuex系列教程:第六讲 Vuex的管理员Module(实战篇)

    写在前面 这一讲是 Vuex 基础篇的最后一讲,也是最为复杂的一讲.如果按照官方来的话,对于新手可能有点难以接受,所以想了下,决定干脆多花点时间,用一个简单的例子来讲解,顺便也复习一下之前的知识点. ...

  3. 日志分析工具ELK(四)

    Logstash收集TCP日志 #Input plugins TCP插件 所需的配置选项 tcp { port =>... } [root@linux-node1 ~]# cat tcp.con ...

  4. 还在写CURD?试试这款基于mybatis-plus的springboot代码生成器

    目录 ⚡Introduction ✔️Release Features Quick Start Examples 1.Controller模板代码示例 2.Service模板代码示例 3.Servic ...

  5. Linux分类

    Linux versions:http://www.cnblogs.com/sammyliu/articles/4832157.html1. Maintained by organization- D ...

  6. 27.rm命令

    rm命令可以删除指定的文件或目录.也可以将某个目录及其下属的所有文件及其子目录均删除掉.对于链接文件,只是删除整个链接文件,而原有文件保持不变. 选项:-f:强制删除. -r:递归处理,将指定目录下的 ...

  7. 在Jetson TX2上显示摄像头视频并使用python进行caffe推理

    参考文章:How to Capture Camera Video and Do Caffe Inferencing with Python on Jetson TX2 与参考文章大部分都是相似的,如果 ...

  8. docker部署gitlab

    Docker部署gitlab 一.前提条件 (1)     存在docker (2)     服务器可以联网(外网) (3)     服务器内存至少4G(内存不够会出现502错误) 内存不足502错误 ...

  9. 「从零单排HBase 09」Hbase的那些数据结构和算法

    在之前学习MySQL的时候,我们知道存储引擎常用的索引结构有B+树索引和哈希索引. 而对HBase的学习,也离不开索引结构的学习,它使用了一种LSM树((Log-Structured Merge-Tr ...

  10. JavaScript键值对集合怎么使用

    JavaScript键值对集合怎么使用 我们可以对此键值对集合分为3种难度 1.简单的使用 var arr = { 'cn': "中国", 'usa': '美国', 'jp': ' ...