Spring Boot 源码剖析 Spring Boot依赖管理 问题:(1)为什么导入dependency时不需要指定版本? Spring Boot项目的父项目依赖spring-boot-starter-parent org.springframework.boot spring-boot-starter-parent 2.5.1 spring-boot-starter-parent中定义了jdk版本,源文件编码方式,Maven打包编译版本等。 1.8 @ ${java.version} ${java.version} UTF-8 UTF-8 并且在build标签中定义了resource资源和pluginManagement ${basedir}/src/main/resources true **/application*.yml **/application*.yaml **/application*.properties ${basedir}/src/main/resources **/application*.yml **/application*.yaml **/application*.properties 里面定义了资源过滤,针对 application 的 yml 、 properties 格式进行了过滤,可以支持不同环境的配置,比如 application-dev.yml 、 application-test.yml 、 application-dev.properties 、 application-dev.properties 等等。 spring-boot-starter-parent项目的父项目是spring-boot-dependencies org.springframework.boot spring-boot-dependencies 2.5.1 在这个项目中定义了大量的依赖项目的版本 5.16.2 2.7.7 1.9.89 2.17.0 1.9.6 3.19.0 4.0.6 4.0.3 3.2.0 1.10.22 ...... spring-boot-dependencies的dependencyManagement节点 在这里,dependencies定义了SpringBoot版本的依赖的组件以及相应版本。 org.apache.activemq activemq-amqp ${activemq.version} org.apache.activemq activemq-blueprint ${activemq.version} ....... 因为存在这个依赖关系所以在我们创建的Spring Boot项目中部分依赖不需要写版本号。 (2)问题2: spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目 运行依赖的JAR包是从何而来的? spring-boot-starter-web 查看spring-boot-starter-web依赖文件源码,核心代码具体如下 org.springframework.boot spring-boot-starter 2.5.1 compile org.springframework.boot spring-boot-starter-json 2.5.1 compile org.springframework.boot spring-boot-starter-tomcat 2.5.1 compile org.springframework spring-web 5.3.8 compile org.springframework spring-webmvc 5.3.8 compile 可见在spring-boot-starter-web依赖启动器中打包了web开发所需要的底层所有依赖。 因此在引入spring-boot-starter-web依赖启动器时,就可以实现web场景开发,而不需要额外导入tomcat服务器及其他web依赖文件。 Spring Boot除了提供Web依赖启动器之外,还提供了其他很多依赖启动器。具体可以到官网查找。 自动配置 自动配置:根据我们添加的jar包依赖,Spring Boot会将一些配置类的bean注册进ioc容器,我们可以在需要的地方直接使用。 Spring Boot是如何进行自动配置的,都把哪些组件进行了自动配置? Spring Boot的启动入口是一个被@SpringBootApplication注解的类的main方法。 @SpringBootApplication 查看@SpringBootApplication源码: // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.core.annotation.AliasFor; @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 { // 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。 @AliasFor( annotation = EnableAutoConfiguration.class ) Class[] exclude() default {}; // 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全 类名字符串数组。 @AliasFor( annotation = EnableAutoConfiguration.class ) String[] excludeName() default {}; // 指定扫描包,参数是包名的字符串数组。 @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; // 扫描特定的包,参数类似是Class类型数组。 @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class[] scanBasePackageClasses() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "nameGenerator" ) Class nameGenerator() default BeanNameGenerator.class; @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; } 从源码可以看出,@SpringBootApplication是一个组合注解,前面4个是注解的元数据信息,后面三个注解是核心:@SpringBootConfiguration、@EnableAutoConfiguration、 @ComponentScan @SpringBootConfiguration 查看源码: @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration @Indexed public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; } 从源码看到,它是一个被@Configuration标记的注解,没有其他的代码。说明它其实就是一个@Configuration的包装,重新命名,功能相同。 被@SpringBootConfiguration 标记的类就是一个配置类。 @EnableAutoConfiguration 查看源码: // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 自动配置包 @AutoConfigurationPackage // Spring的底层注解@Import,给容器中导入一个组件; // 导入的组件是AutoConfigurationPackages.Registrar.class @Import({AutoConfigurationImportSelector.class}) // 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。 public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; // 返回不会被导入到 Spring 容器中的类 Class[] exclude() default {}; // 返回不会被导入到 Spring 容器中的类名 String[] excludeName() default {}; } @EnableAutoConfiguration是一个组合注解:是@AutoConfigurationPackage 和@Import的组合。 image.png Spring中很多以Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的Bean,并加载到IOC容器。 @EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。 它引入了AutoConfigurationImportSelector类。 下面我们继续深入到@AutoConfigurationPackage 和@Import这两个注解中去看看它们做了什么工作。 @AutoConfigurationPackage 查看源码: // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar; import org.springframework.context.annotation.Import; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class})// 导入Registrar中注册的组件 public @interface AutoConfigurationPackage { String[] basePackages() default {}; Class[] basePackageClasses() default {}; } @AutoConfigurationPackage 注解引入Registrar类 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } /** * 注册BeanDefinition */ public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 根据注解元信息,拿到要扫描的包,扫描并注册到BeanDefinitionRegistry中 AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])); } public Set determineImports(AnnotationMetadata metadata) { return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata)); } } register方法: public static void register(BeanDefinitionRegistry registry, String... packageNames) { // 第一次进到这个方法的时候private static final String BEAN = AutoConfigurationPackages.class.getName(); 并没有在register中注册。 if (registry.containsBeanDefinition(BEAN)) { AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN); beanDefinition.addBasePackages(packageNames); } else { // 注册BeanDefinition registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames)); } } 在register中注册的是AutoConfigurationPackages的内部类BasePackagesBeanDefinition。 BasePackagesBeanDefinition源码: static final class BasePackagesBeanDefinition extends GenericBeanDefinition { private final Set basePackages = new LinkedHashSet(); BasePackagesBeanDefinition(String... basePackages) { // Class对象是BasePackages this.setBeanClass(AutoConfigurationPackages.BasePackages.class); this.setRole(2); // 保存基础包 this.addBasePackages(basePackages); } public Supplier getInstanceSupplier() { return () -> { return new AutoConfigurationPackages.BasePackages(StringUtils.toStringArray(this.basePackages)); }; } private void addBasePackages(String[] additionalBasePackages) { this.basePackages.addAll(Arrays.asList(additionalBasePackages)); } } static final class BasePackages { private final List packages; private boolean loggedBasePackageInfo; BasePackages(String... names) { List packages = new ArrayList(); String[] var3 = names; int var4 = names.length; for(int var5 = 0; var5 < var4; ++var5) { String name = var3[var5]; if (StringUtils.hasText(name)) { packages.add(name); } } this.packages = packages; } List get() { if (!this.loggedBasePackageInfo) { if (this.packages.isEmpty()) { if (AutoConfigurationPackages.logger.isWarnEnabled()) { AutoConfigurationPackages.logger.warn("@EnableAutoConfiguration was declared on a class in the default package. Automatic @Repository and @Entity scanning is not enabled."); } } else if (AutoConfigurationPackages.logger.isDebugEnabled()) { String packageNames = StringUtils.collectionToCommaDelimitedString(this.packages); AutoConfigurationPackages.logger.debug("@EnableAutoConfiguration was declared on a class in the package '" + packageNames + "'. Automatic @Repository and @Entity scanning is enabled."); } this.loggedBasePackageInfo = true; } return this.packages; } } BasePackagesBeanDefinition 继承了GenericBeanDefinition,说明它就是一个Spring的BeanDefinition。只是在这个BeanDefinition中保存的Class并不是AutoConfigurationPackages,而是 BasePackages。 因此@EnableAutoConfiguration中的@AutoConfigurationPackage使用@Import({Registrar.class})注解,向Ioc容器中注册了一个BasePackages的Bean定义。 用图来描述一下整个过程: image.png @Import({AutoConfigurationImportSelector.class}) @Import({AutoConfigurationImportSelector.class}) :将 AutoConfigurationImportSelector 这个类导入到 Spring 容器中, AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。 image 可以看到 AutoConfigurationImportSelector 重点是实现了 DeferredImportSelector 接口和各种 Aware 接口,然后 DeferredImportSelector 接口又继承了 ImportSelector 接口。 其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被回调。 自动配置实现逻辑的入口方法: AutoConfigurationImportSelector将会被放到DeferredImportSelectorGrouping中的deferredImports集合中。并且在DeferredImportSelectorGrouping的getImports()方法中进行统一处理。 跟自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping 类的 getImports 方法处. public Iterable getImports() { Iterator var1 = this.deferredImports.iterator(); // 遍历DeferredImportSelectorGrouping的deferredImports集合。AutoConfigurationImportSelector也在这个集合中。 while(var1.hasNext()) { ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next(); // (1)利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类 this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } // (2)经过上面的处理后,然后再进行选择导入哪些配置类 return this.group.selectImports(); } 其中重要的两个步骤: 利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类 经过上面的处理后,然后再进行选择导入哪些配置类 分析自动配置的主要逻辑 AutoConfigurationImportSelector的process方法: // 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类 public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> { return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName()); }); // (1)调用getAutoConfigurationEntry方法得到自动配置类放入 autoConfigurationEntry对象中 AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata); // (2)又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合 this.autoConfigurationEntries.add(autoConfigurationEntry); Iterator var4 = autoConfigurationEntry.getConfigurations().iterator(); // (3)遍历刚获取的自动配置类,放入entries while(var4.hasNext()) { String importClassName = (String)var4.next(); // 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合 this.entries.putIfAbsent(importClassName, annotationMetadata); } } 其中重要的三个步骤: 调用getAutoConfigurationEntry方法得到自动配置类放入 autoConfigurationEntry对象中 又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合 遍历刚获取的自动配置类,放入entries 获取自动配置类方法getAutoConfigurationEntry: protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { AnnotationAttributes attributes = this.getAttributes(annotationMetadata); // (1)得到spring.factories文件配置的所有自动配置类 List configurations = this.getCandidateConfigurations(annotationMetadata, attributes); // 利用LinkedHashSet移除重复的配置类 configurations = this.removeDuplicates(configurations); // 得到要排除的自动配置类,比如注解属性exclude的配置类 // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class) // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据 Set exclusions = this.getExclusions(annotationMetadata, attributes); // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常 this.checkExcludedClasses(configurations, exclusions); // (2)将要排除的配置类移除 configurations.removeAll(exclusions); // (3)对加载到的自动配置类按需要进行过滤 configurations = this.getConfigurationClassFilter().filter(configurations); // (4)获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件 // 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类 // 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发 this.fireAutoConfigurationImportEvents(configurations, exclusions); // (5)将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回 return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } } 其中重要的五个步骤: 得到spring.factories文件配置的所有自动配置类 将要排除的配置类移除 对加载到的自动配置类按需要进行过滤 告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类 将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回 getCandidateConfigurations获取配置文件中的自动配置类过程分析 这个方法中重要的方法是loadFactoryNames,这个方法的作用是加载一些组件的名字。 protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 加载组件名字 List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; } 进入loadFactoryNames方法,它只是做了简单验证,就委托另外一个方法loadSpringFactories 去加载组件名。 public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoader == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); // 真正去加载组件名的方法 return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); } 继续进入loadSpringFactories方法: private static Map> loadSpringFactories(ClassLoader classLoader) { Map> result = (Map)cache.get(classLoader); if (result != null) { return result; } else { HashMap result = new HashMap(); try { // 加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装 为Enumeration类对象 Enumeration urls = classLoader.getResources("META-INF/spring.factories"); //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入 的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中 while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); String[] var10 = factoryImplementationNames; int var11 = factoryImplementationNames.length; for(int var12 = 0; var12 < var11; ++var12) { String factoryImplementationName = var10[var12]; ((List)result.computeIfAbsent(factoryTypeName, (key) -> { return new ArrayList(); })).add(factoryImplementationName.trim()); } } } result.replaceAll((factoryType, implementations) -> { return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); }); cache.put(classLoader, result); return result; } catch (IOException var14) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14); } } } 这个方法的主要作用是遍历整个ClassLoader下所有的jar包下的Spring.factories文件。 image 而在spring-boot-autoconfigure包的META-INF下的spring.factories中保存着springboot的默认提供的自动配置类。 image 我们下面总结下 getAutoConfigurationEntry 方法主要做的事情: 【1】从 spring.factories 配置文件中加载 EnableAutoConfiguration 自动配置类),获取的自动配 置类如图所示。 【2】若 @EnableAutoConfiguration 等注解标有要 exclude 的自动配置类,那么再将这个自动配置类排除掉; 【3】排除掉要 exclude 的自动配置类后,然后再调用 filter 方法进行进一步的过滤,再次排除一些 不符合条件的自动配置类; 【4】经过重重过滤后,此时再触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类; 【5】 最后再将符合条件的自动配置类返回。 总结了 getAutoConfigurationEntry 方法主要的逻辑后,我们再来细看一下 AutoConfigurationImportSelector 的 filter 方法: List filter(List configurations) { long startTime = System.nanoTime(); // 将从spring.factories中获取的自动配置类转出字符串数组 String[] candidates = StringUtils.toStringArray(configurations); boolean skipped = false; Iterator var6 = this.filters.iterator(); int i; while(var6.hasNext()) { AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var6.next(); // 判断各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的 // @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配, boolean[] match = filter.match(candidates, this.autoConfigurationMetadata); // 遍历match数组,注意match顺序跟candidates的自动配置类一一对应 for(i = 0; i < match.length; ++i) { // 若有不匹配的话 if (!match[i]) { candidates[i] = null; // 标注skipped为true skipped = true; } } } // 这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和 OnWebApplicationCondition过滤后,全部都匹配的话,则全部原样返回 if (!skipped) { return configurations; } else { // 建立result集合来装匹配的自动配置类 List result = new ArrayList(candidates.length); String[] var12 = candidates; int var14 = candidates.length; for(i = 0; i < var14; ++i) { String candidate = var12[i]; // 符合条件的自动配置类,此时添加到result集合中 if (candidate != null) { result.add(candidate); } } if (AutoConfigurationImportSelector.logger.isTraceEnabled()) { int numberFiltered = configurations.size() - result.size(); AutoConfigurationImportSelector.logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); } // 最后返回符合条件的自动配置类 return result; } } AutoConfigurationImportSelector 的 filter 方法主要做的事情就是调用AutoConfigurationImportFilter 接口的match 方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnClass ,@ConditionalOnBean 或 @ConditionalOnWebApplication 是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。 我们现在知道 AutoConfigurationImportSelector 的 filter 方法主要做了什么事情就行了,现在先不用研究的过深 有选择的导入自动配置类 this.group.selectImports 方法是如何进一步有选择的导入自动配置类的。 public Iterable selectImports() { if (this.autoConfigurationEntries.isEmpty()) { return Collections.emptyList(); } else { // 这里得到所有要排除的自动配置类的set集合 Set allExclusions = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet()); // 这里得到经过滤后所有符合条件的自动配置类的set集合 Set processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new)); // 移除掉要排除的自动配置类 processedConfigurations.removeAll(allExclusions); // 对标注有@Order注解的自动配置类进行排序 return (Iterable)this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> { return new Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName); }).collect(Collectors.toList()); } } selectImports 方法的自动配置类再进一步排除 exclude 的自动配置类,然后再排序 最后,我们再总结下SpringBoot自动配置的原理,主要做了以下事情: 从spring.factories配置文件中加载自动配置类; 加载的自动配置类中排除掉 @EnableAutoConfiguration 注解的 exclude 属性指定的自动配置类; 然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果; 然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来分别记录符合条件和 exclude 的自动配置类。 最后spring再将最后筛选后的自动配置类导入IOC容器中 画图梳理一下这个流程 image.png 总结: 在当前Spring Boot工程中,各个组件(jar包),都会存在一个名称为META-INF的目录,目录中有一个sping.factories文件。 在这个文件中会配置工厂类的全路径如MyBatis中配置了org.mybatis.spring.boot.autoconfigure.MyBatisAutoConfiguration。 Spring Boot 通过@EnableAutoConfiguration注解收集配置工厂类 由Spring创建bean实例存到IoC容器中。 image 条件注解 @Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。 @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。 @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。 @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式 的条件判断。 @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。 @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。 @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。 @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。 @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。 @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。 @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。 @ConditionalOnResource:当类路径下有指定的资源时触发实例化。 @ConditionalOnJndi:在JNDI存在的条件下触发实例化。 @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首 选的Bean时触发实例化。 @ComponentScan @ConponentScan用于配置扫描Spring的扫包基础路径的注解。默认扫描被这个注解标记的类的目录及子目录。 @SpringBootApplication中包含了@ConponentScan,因此它具备@ConponentScan相同的功能,因此被@SpringBootApplication注解标记了的类的类路径及其子路径上的bean都能被扫描到。这也是为什么非这个目录及其子目录中的bean不能被扫描的原因。 SpringApplication初始化过程 @SpringBootApplication中的@ComponentScan是在什么时候被扫描到的? SpringBoot项目的main方法: @SpringBootApplication @EnableConfigurationProperties public class SpringBootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args); } } 从run方法开始追踪: public static ConfigurableApplicationContext run(Class primarySource, String... args) { return run(new Class[]{primarySource}, args); } 调用了另外一个run方法: public static ConfigurableApplicationContext run(Class[] primarySources, String[] args) { return (new SpringApplication(primarySources)).run(args); } 这个run方法构建了一个SpringApplication类的实例,我们继续来看下SpringApplication类的构造函数 SpringApplication构造函数 public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { this.sources = new LinkedHashSet(); this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; this.addCommandLineProperties = true; this.addConversionService = true; this.headless = true; this.registerShutdownHook = true; this.additionalProfiles = Collections.emptySet(); this.isCustomEnvironment = false; this.lazyInitialization = false; this.applicationContextFactory = ApplicationContextFactory.DEFAULT; this.applicationStartup = ApplicationStartup.DEFAULT; this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); // 1、推断应用类型:servlet、reactive、none this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories(); // 2、初始化META-INF/spring.factories文件中配置的ApplicationContextInitializer this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 3、初始化classpath下的所有已配置的ApplicationListener this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); // 4、推断出main方法的类名。 this.mainApplicationClass = this.deduceMainApplicationClass(); } 判断应用类型 private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"}; static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) { return REACTIVE; } else { String[] var0 = SERVLET_INDICATOR_CLASSES; int var1 = var0.length; for(int var2 = 0; var2 < var1; ++var2) { String className = var0[var2]; if (!ClassUtils.isPresent(className, (ClassLoader)null)) { return NONE; } } return SERVLET; } } 判断应用类型主要分三个步骤: 1、判断类路径中有没有DispatcherHandler,DispatcherServlet,ServletContainer:如果有DispatcherHandler,没有DispatcherServlet,ServletContainer则是REACTIVE(响应式编程)类型的应用。 2、如果不满足条件1,如果类路径不包含Servlet类或不包含ConfigurableWebApplicationContext类,则是NONE(不是一个web应用)类型应用。 如果条件1、2都不满足,那它就是一个Servlet(web应用,需要启动内置servlet容器)应用。 初始化ApplicationContextInitializer this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); private Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) { ClassLoader classLoader = this.getClassLoader(); // 从MATA-INF/spring.factories文件中读取所有ApplicationContextInitializer类全路径为key的所有类全路径 Set names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 反射实例化这些ApplicationContextInitializer类 List instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } 主要完成了两步操作: 从MATA-INF/spring.factories文件中读取所有ApplicationContextInitializer类全路径为key的所有类全路径。 反射实例化上一步获取到的类,并放入到一个集合中。 初始化classpath下的所有ApplicationListener this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); `` 加载过程跟初始化器一样 主要完成了两步操作: - 从MATA-INF/spring.factories文件中读取所有ApplicationListener类全路径为key的所有类全路径。 - 反射实例化上一步获取到的类,并放入到一个集合中。 #### 根据调用栈,推断出main方法的类 ```java this.mainApplicationClass = this.deduceMainApplicationClass(); private Class deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace(); StackTraceElement[] var2 = stackTrace; int var3 = stackTrace.length; for(int var4 = 0; var4 < var3; ++var4) { StackTraceElement stackTraceElement = var2[var4]; if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException var6) { } return null; } 这一步的主要功能是: 根据调用栈,找到main方法所在类的class对象,存储到成员变量。 SpringApplication.run方法流程介绍 SpringApplication对象构建完后调用run方法,run方法代码如下: public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); DefaultBootstrapContext bootstrapContext = this.createBootstrapContext(); ConfigurableApplicationContext context = null; this.configureHeadlessProperty(); //从META-INF/spring.factories中获取监听器 //1、获取并启动监听器 SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //2、构造应用上下文环境 ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments); this.configureIgnoreBeanInfo(environment); Banner printedBanner = this.printBanner(environment); //3、初始化应用上下文 context = this.createApplicationContext(); context.setApplicationStartup(this.applicationStartup); //4、刷新应用上下文前的准备阶段 this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); //5、刷新应用上下文 this.refreshContext(context); //6、刷新应用上下文后的扩展接口 this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, listeners); throw new IllegalStateException(var10); } try { listeners.running(context); return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } } 在以上的代码中,启动过程中的重要步骤共分为六步 : 1、获取并启动监听器 2、构造应用上下文环境 3、初始化应用上下文 4、刷新应用上下文前的准备阶段 5、刷新应用上下文 6、刷新应用上下文后的扩展接口 获取并启动监听器 事件机制在Spring是很重要的一部分内容,通过事件机制我们可以监听Spring容器中正在发生的一些事件,同样也可以自定义监听事件。Spring的事件为Bean和Bean之间的消息传递提供支持。当一个对象处理完某种任务后,通知另外的对象进行某些处理,常用的场景有进行某些操作后发送通知,消息、邮件等情况。 获取监听器的入口代码: SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); 进入到方法中: private SpringApplicationRunListeners getRunListeners(String[] args) { Class[] types = new Class[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, // 从META-INF/spring.factories文件中获取监听器 getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup); } 从代码中看到,在构建SpringApplicationRunListeners实例之前调用了一个我们非常熟悉的方法:getSpringFactoriesInstances,该方法的主要作用是从spring.factories文件中加载SpringApplicationRunListener类全路径为key的所有类,并实例化。 private Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates // 根据传入的type类型,使用type的类名全路径作为key,到META-INF/spring.factories文件中去查找值,并放到names中 Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 将查找到的类通过反射实例化 List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // 按order注解顺序排序 AnnotationAwareOrderComparator.sort(instances); return instances; } 获取到监听器后,启动监听器: listeners.starting(bootstrapContext, this.mainApplicationClass); 构造应用上下文环境 用上下文环境包括什么呢?包括计算机的环境,Java环境,Spring的运行环境,Spring项目的配置(在SpringBoot中就是那个熟悉的application.properties/yml)等等。 private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { //创建并配置相应的环境 ConfigurableEnvironment environment = this.getOrCreateEnvironment(); //根据用户配置,配置 environment系统环境 this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs()); // 启动相应的监听器,其中一个重要的监听器 ConfigFileApplicationListener 就是加载项目配置文件的监听器。 ConfigurationPropertySources.attach((Environment)environment); listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment); DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment); Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties."); this.bindToSpringApplication((ConfigurableEnvironment)environment); if (!this.isCustomEnvironment) { environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass()); } ConfigurationPropertySources.attach((Environment)environment); return (ConfigurableEnvironment)environment; } getOrCreateEnvironment方法做了什么? private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: // 如果应用类型是SERVLET,则创建Servlet类型的环境 return new ApplicationServletEnvironment(); case REACTIVE: // 如果应用类型是响应式编程的,则创建Reactive类型的环境 return new ApplicationReactiveWebEnvironment(); default: return new ApplicationEnvironment(); } } ApplicationServletEnvironment类型的继承关系: image /** * Template method delegating to * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order. * Override this method for complete control over Environment customization, or one of * the above for fine-grained control over property sources or profiles, respectively. * @param environment this application's environment * @param args arguments passed to the {@code run} method * @see #configureProfiles(ConfigurableEnvironment, String[]) * @see #configurePropertySources(ConfigurableEnvironment, String[]) */ protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { environment.setConversionService(new ApplicationConversionService()); } // 将main方法的参数分组到SimpleCommandLinePropertySource中 configurePropertySources(environment, args); // 激活相关的配置文件 configureProfiles(environment, args); } 初始化应用上下文 /** *用于创建 {@link ApplicationContext} 的策略方法 * Strategy method used to create the {@link ApplicationContext}. By default this * method will respect any explicitly set application context class or factory before * falling back to a suitable default. * @return the application context (not yet refreshed) * @see #setApplicationContextClass(Class) * @see #setApplicationContextFactory(ApplicationContextFactory) */ protected ConfigurableApplicationContext createApplicationContext() { // 根据应用类型创建对应的 ApplicationContext实例 return this.applicationContextFactory.create(this.webApplicationType); } 调试进入: org.springframework.boot.ApplicationContextFactory ApplicationContextFactory DEFAULT = (webApplicationType) -> { try { switch (webApplicationType) { case SERVLET: // 应用类型是Servlet时,new一个AnnotationConfigServletWebServerApplicationContext上下文实例对象 return new AnnotationConfigServletWebServerApplicationContext(); case REACTIVE: return new AnnotationConfigReactiveWebServerApplicationContext(); default: return new AnnotationConfigApplicationContext(); } } catch (Exception ex) { throw new IllegalStateException("Unable create a default ApplicationContext instance, " + "you may need a custom ApplicationContextFactory", ex); } }; AnnotationConfigServletWebServerApplicationContext的类继承关系: image GenericApplicationContext是AnnotationConfigServletWebServerApplicationContext的父类。 创建对象的时候,创建了一个reader和一个scanner: /** * Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs * to be populated through {@link #register} calls and then manually * {@linkplain #refresh refreshed}. */ public AnnotationConfigServletWebServerApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } 另外在调用AnnotationConfigServletWebServerApplicationContext构造函数之前,要先调用父类的构造函数: public GenericApplicationContext() { this.customClassLoader = false; this.refreshed = new AtomicBoolean(); this.beanFactory = new DefaultListableBeanFactory(); } AnnotationConfigServletWebServerApplicationContext中的IoC容器在其父类的构造方法中被实例化。 image 刷新应用上下文前的准备阶段 刷新应用上下文前的准备阶段,就是想上下文context中设置一些属性,完成bean对象的创建。 private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 设置容器环境 context.setEnvironment(environment); // 执行容器后置处理器,向context中设置值 postProcessApplicationContext(context); // 遍历初始化器,执行初始化 applyInitializers(context); // 向各个监听器去发送容器准备好的事件 listeners.contextPrepared(context); bootstrapContext.close(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } // Load the sources // 获取所有启动类 Set sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); } Set sources = getAllSources();拿到启动类,我们来看下它是怎么拿到的? public Set getAllSources() { Set allSources = new LinkedHashSet<>(); // primarySources在SpringApplication的run方法中传入的 if (!CollectionUtils.isEmpty(this.primarySources)) { allSources.addAll(this.primarySources); } if (!CollectionUtils.isEmpty(this.sources)) { allSources.addAll(this.sources); } return Collections.unmodifiableSet(allSources); } 从源码中看到:它是将primarySources和sources两个集合合并后返回。因此primarySources和sources中存放的就是启动类。 下面看看它们在哪里被赋值的: 在SpringApplication的构造方法中,将构造方法中的参数直接添加到了primarySources集合中。 public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); } 另外有一个方法也涉及到往这个集合中添加值: public void addPrimarySources(Collection> additionalPrimarySources) { this.primarySources.addAll(additionalPrimarySources); } 但是通过findUseage发现,在启动流程中,这个方法还没有被调用过。因此,当前状态下primarySources集合中只有SpringApplication.run(SpringBootDemoApplication.class, args); 参数中传入的启动类。 另外一个集合sources的只在一个方法中赋值: public void setSources(Set sources) { Assert.notNull(sources, "Sources must not be null"); this.sources = new LinkedHashSet<>(sources); } 这个方法只被SampleSpringXmlApplication类中的main方法调用,而Spring Boot的启动类是通过SpringApplication的run方法启动的。因此sources集合是空的。 Set sources = getAllSources();在Spring Boot工程启动过程中,拿到的是SpringBoot启动类,就是传入到SpringApplication.run()方法的第一个参数。 验证: 断点打在run方法 image primarySources 集合中只有一个SpringBootDemoApplication类对象 image 这个类对象正好是我们的启动类 image 断点打到getAllResource image allSources中只有我们的启动类对象,sources集合为空,验证了我们的结论。 image 接着看load()方法 /** * Load beans into the application context. * @param context the context to load beans into * @param sources the sources to load */ protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } // 创建BeanDefinitionLoader BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources); if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator); } if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader); } if (this.environment != null) { loader.setEnvironment(this.environment); } loader.load(); } 在创建BeanDefinitionLoader之前,先获取BeanDefinitionRegistery: private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) { if (context instanceof BeanDefinitionRegistry) { return (BeanDefinitionRegistry) context; } if (context instanceof AbstractApplicationContext) { return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory(); } throw new IllegalStateException("Could not locate BeanDefinitionRegistry"); } 其实就是将context中的IOC容器直接拿出来,并且转成BeanDefinitionRegistry。在前面看AnnotationConfigServletWebApplicationContext类继承关系的时候知道,它实现了BeanDefinitionRegistry接口,因此可以强转。并且可以验证这里就是AnnotationConfigServletWebApplicationContext。 image BeanDefinitionRegistry定义了很重要的方法registerBeanDefinition(),该方法将BeanDefinition 注册进DefaultListableBeanFactory容器的beanDefinitionMap中。 现在再来看创建BeanDefinitionLoader的过程: protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) { return new BeanDefinitionLoader(registry, sources); } 构造方法: /** * Create a new {@link BeanDefinitionLoader} that will load beans into the specified * {@link BeanDefinitionRegistry}. * @param registry the bean definition registry that will contain the loaded beans * @param sources the bean sources */ BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) { Assert.notNull(registry, "Registry must not be null"); Assert.notEmpty(sources, "Sources must not be empty"); this.sources = sources; //注解形式的Bean定义读取器 比如:@Configuration @Bean @Component @Controller @Service等等 this.annotatedReader = new AnnotatedBeanDefinitionReader(registry); //XML形式的Bean定义读取器 this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null); this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null); //类路径扫描器 this.scanner = new ClassPathBeanDefinitionScanner(registry); //扫描器添加排除过滤器 this.scanner.addExcludeFilter(new ClassExcludeFilter(sources)); } 在该方法中创建了reader和scanner,这两个很熟悉的东西,后面肯定会使用reader去读取配置,scaner进行包扫描。 接着看loader.load();方法: /** * Load the sources into the reader. */ void load() { // 遍历启动类,分别进行load for (Object source : this.sources) { load(source); } } private void load(Object source) { Assert.notNull(source, "Source must not be null"); // 从calss加载 if (source instanceof Class) { load((Class) source); return; } // 从resource加载 if (source instanceof Resource) { load((Resource) source); return; } // 从package加载 if (source instanceof Package) { load((Package) source); return; } // 从CharSequence加载 if (source instanceof CharSequence) { load((CharSequence) source); return; } throw new IllegalArgumentException("Invalid source type " + source.getClass()); } Spring Boot应用启动时会从class加载。 private void load(Class source) { if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { // Any GroovyLoaders added in beans{} DSL can contribute beans here GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans()); } // 判断启动类是否符合注册条件 if (isEligible(source)) { this.annotatedReader.register(source); } } 什么是符合注册条件的: private boolean isEligible(Class type) { // 不是匿名的,就是没有名字的类。不是groovy闭包。不是没有构造方法的 return !(type.isAnonymousClass() || isGroovyClosure(type) || hasNoConstructors(type)); } 这里,启动类是符合注册条件的。 所以启动类会被注册到IoC容器中。 this.annotatedReader.register(source); 而annotatedReader是BeanDefinitionLoader构造函数中被实例化的,并且它是Spring Framework中的类,所以注册过程是由Spring Framework完成的 BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) { Assert.notNull(registry, "Registry must not be null"); Assert.notEmpty(sources, "Sources must not be empty"); this.sources = sources; // AnnotatedBeanDefinitionReader是Spring Framework中的组件 this.annotatedReader = new AnnotatedBeanDefinitionReader(registry); this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null); this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null); this.scanner = new ClassPathBeanDefinitionScanner(registry); this.scanner.addExcludeFilter(new ClassExcludeFilter(sources)); } 刷新应用上下文 刷新上下文代码: private void refreshContext(ConfigurableApplicationContext context) { if (this.registerShutdownHook) { shutdownHook.registerApplicationContext(context); } // 核心方法 refresh(context); } 从源码可以看到,刷新上下文的核心方法是refresh方法 /** * Refresh the underlying {@link ApplicationContext}. * @param applicationContext the application context to refresh */ protected void refresh(ConfigurableApplicationContext applicationContext) { applicationContext.refresh(); } 直接直接调用了application的refresh方法 public void refresh() throws BeansException, IllegalStateException { synchronized(this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); // 第⼀步:刷新前的预处理 this.prepareRefresh(); /* 第⼆步: 获取BeanFactory;默认实现是DefaultListableBeanFactory 加载BeanDefition 并注册到 BeanDefitionRegistry */ ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); // 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等) this.prepareBeanFactory(beanFactory); try { // 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作 this.postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); // 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean this.invokeBeanFactoryPostProcessors(beanFactory); // 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执⾏ this.registerBeanPostProcessors(beanFactory); beanPostProcess.end(); // 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析); this.initMessageSource(); // 第⼋步:初始化事件派发器 this.initApplicationEventMulticaster(); // 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑 this.onRefresh(); // 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean this.registerListeners(); /* 第⼗⼀步: 初始化所有剩下的⾮懒加载的单例bean 初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性) 填充属性 初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法) 调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处 */ this.finishBeanFactoryInitialization(beanFactory); /* 第⼗⼆步: 完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事 件 (ContextRefreshedEvent) */ this.finishRefresh(); } catch (BeansException var10) { if (this.logger.isWarnEnabled()) { this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10); } this.destroyBeans(); this.cancelRefresh(var10); throw var10; } finally { this.resetCommonCaches(); contextRefresh.end(); } } } invokeBeanFactoryPostProcessors(beanFactory);(重点) IoC容器的初始化过程包括三个步骤,在invokeBeanFactoryPostProcessors()方法中完成了IoC容 器初始化过程的三个步骤。 1,第一步:Resource定位 在SpringBoot中,我们都知道他的包扫描是从主类所在的包开始扫描的,prepareContext() 方法中,会先将主类解析成BeanDefinition,然后在refresh()方法的 invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition获取basePackage的路 径。这样就完成了定位的过程。其次SpringBoot的各种starter是通过SPI扩展机制实现的自动装 配,SpringBoot的自动装配同样也是在invokeBeanFactoryPostProcessors()方法中实现的。还有 一种情况,在SpringBoot中有很多的@EnableXXX注解,细心点进去看的应该就知道其底层是 @Import注解,在invokeBeanFactoryPostProcessors()方法中也实现了对该注解指定的配置类的 定位加载。 常规的在SpringBoot中有三种实现定位,第一个是主类所在包的,第二个是SPI扩展机制实现 的自动装配(比如各种starter),第三种就是@Import注解指定的类。(对于非常规的不说了) 2,第二步:BeanDefinition的载入 在第一步中说了三种Resource的定位情况,定位后紧接着就是BeanDefinition的分别载入。 所谓的载入就是通过上面的定位得到的basePackage,SpringBoot会将该路径拼接成: classpath:com/lagou/**/.class这样的形式,然后一个叫做 xPathMatchingResourcePatternResolver的类会将该路径下所有的.class文件都加载进来,然后 遍历判断是不是有@Component注解,如果有的话,就是我们要装载的BeanDefinition。大致过 程就是这样的了。 3、第三个过程:注册BeanDefinition 这个过程通过调用上文提到的BeanDefinitionRegister接口的实现来完成。这个注册过程把载入 过程中解析得到的BeanDefinition向IoC容器进行注册。通过上文的分析,我们可以看到,在IoC容 器中将BeanDefinition注入到一个ConcurrentHashMap中,IoC容器就是通过这个HashMap来持 有这些BeanDefinition数据的。比如DefaultListableBeanFactory 中的beanDefinitionMap属性。 OK,总结完了,接下来我们通过代码看看具体是怎么实现的。 刷新应用上下文后的扩展接口 /** * Called after the context has been refreshed. * @param context the application context * @param args the application arguments */ protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { } 扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。 你可能感兴趣的:(Spring Boot源码剖析之Spring Boot源码剖析) Spring Boot中使用RabbitMQ(2) D1561691 程序员java-rabbitmqspringbootrabbitmq 《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!MessageBroker与AMQP简介MessageBroker是一种消息验证、传输、路由的架构模式,其设计目标主要应用于下面这些场景:消息路由到一个或多个目的地消息转化为其他的表现方式执行消息的聚集、消息的分解,并将结果发送到他们的目的地,然后重新组合相应返回给消息用户调用Web服务来检索数据响 Go算法之奇偶排序 思远久安 Go数据结构与算法小白入门算法golang数据结构后端排序算法 一、什么是奇偶排序(以下排序步骤来自文心一言加上本人的一些理解,我觉得ai讲的比较透彻)奇偶排序步骤:初始化:给定一个待排序的数组。奇数轮次遍历:从索引1(即第二个元素,因为索引从0开始)开始,以步长为2(i+2)遍历数组。比较当前元素与其后一个元素(即索引为i和i+2的元素)。如果当前元素大于后一个元素,则交换它们的位置。偶数轮次遍历:从索引0开始,同样以步长为2遍历数组。执行与奇数轮次相同的比 Spring框架篇 yadanuof yy的学习之路springjava数据库 Bean线程单例bean不是线程安全的,Spring框架中有一个@Scope注解,默认的值就是singleton,单例的。因为一般在spring的bean的中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决AOPAOP面向切面编程,用于处理那些与业务无关,但对多个对象产生影响的公共行为和逻辑,将其抽取并封装为一个可重 Acwing-基础算法课笔记之搜索与图论(spfa算法) 不会敲代码的狗 Acwing基础算法课笔记图论算法笔记 Acwing-基础算法课笔记之搜索与图论(spfa算法)一、spfa算法1、概述2、模拟过程3、spfa算法模板(队列优化的Bellman-Ford算法)4、spfa算法模板(判断图中是否存在负环)一、spfa算法1、概述单源最短路径算法,处理负权边的spfa算法,一般时间复杂度为O(m)O(m)O(m),最坏为O(nm)O(nm)O(nm)。1、建立一个队列,初始化队列里只有起始点(源点);2、 Spring——Spring开发实战经验(1) 庄小焱 Spring框架后端spring系统实战 摘要文章主要介绍了Swagger作为API文档生成和测试工具的功能,包括自动生成API文档、提供可视化调试界面、促进前后端协作、支持OpenAPI规范等。同时,还提及了SpringBoot与Swagger3的实战应用,以及Spring开发中其他相关技术内容,如@Resource与@Autowired的区别、Druid监控配置、切面日志示例等。1.Swagger-API文档生成和测试工具Swagge 算法学习笔记之数学基础 threesevens 算法与数据结构算法 例1(最小公倍数与最大公约数)计算最小公倍数公式:LCM(A,B)=A*B/GCD(A,B)A与B的最小公倍数等于A*B除以A与B的最大公约数计算最大公约数:辗转相除法原理:设A与B的最大公约数为x,则A是x的倍数,B也是x的倍数,令A=ax,B=bx,A/B取整为c,则A-cB=(a-bc)x。即A与B的余数也是x的倍数 intgcd(inta,intb) { inttemp; whil PyQt5之按钮 threesevens qtpyqt 1.QAbstractButton(1)描述所有按钮控件的基类,提供按钮的通用功能,继承自QWidget(2)功能作用1)提示文本setText(str)#设置按钮提示文本text()#获取按钮提示文本案例fromPyQt5.Qtimport*importsysapp=QApplication(sys.argv)window=QWidget()window.setWindowTitle("提示文本 spring security 实现短信登录---若依RuoYi整合短信验证码登录 菜鸡且互啄69 springjava后端 背景:若依默认使用账号密码进行登录,但是咱们客户需要增加一个短信登录功能,即在不更改原有账号密码登录的基础上,整合短信验证码登录。本案例基于RuoYi-Vue版本实现,其他版本应该大同小异。本文参考http://t.csdnimg.cn/go78Z1自定义短信登录token验证仿照UsernamePasswordAuthenticationToken类,编写短信登录token验证。我们自己构造的这 算法学习笔记之贪心算法 threesevens 算法与数据结构算法笔记贪心算法 导引(硕鼠的交易)硕鼠准备了M磅猫粮与看守仓库的猫交易奶酪。仓库有N个房间,第i个房间有J[i]磅奶酪并需要F[i]磅猫粮交换,硕鼠可以按比例来交换,不必交换所有的奶酪计算硕鼠最多能得到多少磅奶酪。输入M和N表示猫粮数量和房间数量,随后输入N个房间,每个房间包括奶酪数和猫粮数Input 53 72 43 52 -1-1Output 13.333解法:计算每个房间的奶酪与猫粮之比,比值越大硕鼠收益越 C#学习之DateTime 类 零壹电子 C#DateTimec#学习开发语言DateTime 目录一、DateTime类的常用方法和属性的汇总表格二、常用方法程序示例1.获取当前本地时间2.获取当前UTC时间3.格式化日期和时间4.获取特定部分的时间5.获取时间戳6.获取时区信息三、总结一、DateTime类的常用方法和属性的汇总表格在C#中,获取本地系统时间非常简单。可以使用DateTime类来获取当前日期和时间。DateTime类提供了丰富的属性和方法,用于处理日期和时间。常用的方法包 C++14新特性之deprecated 画个逗号给明天" C++14新特性c++开发语言 1.介绍在C++中,deprecated是一种标记,用于指示某个函数、类、变量或特性已经过时或不推荐使用。通过标记为deprecated,开发者可以在编译时收到警告,提醒他们避免使用这些过时的功能。2.deprecated语法C++14引入了[[deprecated]]属性,用于标记过时的功能。语法如下:(1)标记函数[[deprecated("Thisfunctionisdeprecated.U 【计算机网络知识 之 OSI七层模型及tcp/ip模型】 皮卡丘的blue 计算机网络知识tcp/ip网络网络协议 文章目录前言OSI七层模型TCP/IP协议族数据封装、解封装过程每一层的数据格式设备与层的对应关系各层间的通信总结前言环境准备:linux操作系统VMwareWorkstation16.xCentos7OSI七层模型国际标准化组织(InternationalStandardOrganization,ISO),于1984年颁布了开放系统互联(OpenSystemInterconnection,OSI 本地DeepSeek模型GGUF文件转换为PyTorch格式 搏博 pytorch人工智能python机器学习windows深度学习 接前文,我们在本地Windows系统上,基于GGUF文件部署了DeepSeek模型(DeepSeek-R1-Distill-Qwen-1.5B.gguf版本),但是GGUF是已经量化的版本,我们除了对其进行微调之外,无法对其训练,那么还有没有其他办法对本地的GGUF部署的DeepSeek模型进行训练呢?今天我们就反其道而行之,将GGUF文件转换为PyTorch格式再训练。前提:已经部署好了Deep Linux基础之文件权限的八进制表示法 vortex5 linux运维服务器 1.Linux文件权限概述在Linux中,每个文件或目录都有三种基本权限,分别是:读权限-r:允许查看文件内容。写权限-w:允许修改文件内容。执行权限-x:允许执行文件或进入目录。每个文件或目录的权限会根据三个用户类别来分配:所有者(user,u):文件的创建者或指定的所有者(属主)。同组用户(group,g):与文件所有者属于同一组的用户(属组)。其他用户(others,o):所有不属于文件所有 C#学习之数据转换 零壹电子 C#数据类型转换学习c#数据类型转换 目录一、创作说明二、数据类型之间的转换1.数据类型之间的转换表格2.代码示例三、进制之间的转换1.进制之间的转换表格2.代码示例四、ASCII编码和字符之间的转换1.ASCII编码和字符之间的转换表格2.代码示例五、总结一、创作说明C#大多数时候都是和各种数据打交道,本文汇总数据转换的表格,包括常用方法、代码示例以及详细描述。涵盖了数据类型之间的转换、进制之间的转换、ASCII编码和字符之间的转换 Spring Boot 整合 log4j2 日志配置教程 m0_74824517 面试学习路线阿里巴巴springbootlog4j单元测试 文章目录前言一、常用日志框架二、配置参数介绍1.日志级别2.输出形式3.日志格式3.1PatternLayout自定义日志布局三、Log4j2配置详解1.根节点Configuration2.Appenders节点2.1Console节点2.2File节点2.3RollingFile节点2.3.1ThresholdFilter节点2.3.2Policies节点2.3.3ThresholdFilter js 使用缓存判断在规定时间内显示一次弹框 洪洪呀 javascript缓存开发语言 js使用缓存判断在规定时间内显示一次弹框功能拆分,新用户注册完成登录跳转首页,js根据注册时间判断显示一个新手指引的弹窗,只在注册当天登录且显示一次jQuery(document).ready(function($){getWinnerModalShow()});//新手指引functiongetWinnerModalShow(){varwinnerModal=newbootstrap.Modal .NET 9.0 的 Blazor Web App 项目,Bootstrap Blazor 全局异常 <ErrorLogger> 使用备忘 cqths BootstrapBlazorBlazorWebAppbootstrap前端c#.net 一、全局异常通过组件实现,可以对全局的日志、异常进行统一输出,该组件【已经包含】在中,使用了组件包裹的razor组件【不用】再额外添加组件包裹。二、全局异常默认已经开启、后台自动生效,【不用】在项目中做其他任务额外操作,项目发布后执行时,自动拦截异常,项目不会因为发生异常而崩溃、导致不可用,可能会在页面顶端显示全局异常的简要说明,如下图所示。三、如果要显示全局异常的详细说明,可以在appsetti Linux--shell脚本之正则表达式 neo_will_mvp 正则表达式Shell项目实战代码shell正则表达式 一、正则表达式的概念及特点:正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。规定一些特殊语法表示字符类、数量限定符和位置关系,然后用这些特殊语法和普通字符一起表示一个模式,这就是正则表达式(RegularExpression)。给定一个正则表达式和另一个字符串,我们可以达到如下的 尚硅谷课程【笔记】——大数据之Zookeeper【一】 赶紧写完去睡觉 大数据生态圈大数据zookeeperlinux 课程视频:【尚硅谷Zookeeper教程】一、Zookeeper入门概述Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。Zookeeper从设计模式角度理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责同志已经在Zookeeper上注册的哪些观察者做出 Java面试突击3(3):Java基础面试--JVM CodingALife 社招面经java 1、JVM有哪⼏块内存区域?Java8之后对内存分代做什么改进?Version:0.9StartHTML:0000000105EndHTML:0000003884StartFragment:0000000141EndFragment:0000003844集合、并发、spring框架,期待着我对这些基础的东西做⼀些很深⼊的,很⽜X的讲解,基于框架写⼀些代码,完事⼉了之后,就会把代码进⾏⼀个部署,⼀般 C/C++基础讲解(五)之基础例程2篇 五一编程 C/C++基础c语言c++开发语言数据结构算法 C/C++基础讲解(五)之基础例程2篇程序之美前言很多时候,特别是刚步入大学的学子们,对于刚刚开展的计算机课程基本上是一团迷雾,想要弄明白其中的奥秘,真的要花费一些功夫,我和大家一样都是这么啃过来的,从不知到知知,懵懂到入门,每一步都走的很艰辛,课程上,大学老师基本上讲解上机实操得时间特别有限,而计算机课程又着重理解和实操,所以我们学习起来会有些吃力,或许朋友会说,你有什么能力来教我们听懂,你有什 Disconnected from the target VM, address: ‘127.0.0.1:64555‘, transport: ‘socket‘解决方案 It_BeeCoder SpringBoot000项目开发问题整理springboot 问题描述今天在启动SpringBoot项目的时候,报以下错误:DisconnectedfromthetargetVM,address:'127.0.0.1:64555',transport:'socket'解决方案经排查,是将@Value("${}")误写为@Resource导致的。 启动Springboot项目maven命令 It_BeeCoder SpringBoot 1mvncleancompile,将项目重新编译2mvninstall,打包3mvnspring-boot:run,启动项目4mvnpackage,打成war包 钢铁行业设备智能运维实战:基于DuodooBMS+SKF的减速机全生命周期管理方案 邹工转型手札 风吟九宵企业信息化Duodoo开源运维数据库人工智能制造开源 (导语:在钢铁行业"设备即产能"的竞争格局下,某大型钢铁集团通过DuodooBMS+SKFObseverPhoenixAPI系统实现核心设备预测性维护,热轧产线非计划停机减少42%,设备综合效率OEE提升17%)一、钢铁企业设备管理之痛某年产800万吨的钢铁联合企业热轧车间,12台关键减速机连续发生异常磨损事故:2023年Q1因1#摆剪减速机轴承失效导致非计划停机23小时,直接损失超200万元传统 Springboot集成pagehelper-spring-boot-starter分页插件 帅大大的架构之路 #Java笔记springboot分页插件 Springboot集成pagehelper-spring-boot-starter1.添加依赖com.github.pagehelperpagehelper-spring-boot-starter1.4.32.application.yml配置#pagehelper配置pagehelper:helperDialect:mysqlreasonable:truesupportMethodsArgum 网络原理之HTTP协议,及理解Cookie和Session的区别 qq_41603622 网络httpcookiesessionhttps 文章目录一、HTTP原理简介二、HTTP内容简介1.认识URL2.HTTP协议格式3.HTTP的方法4.HTTP的状态码通过Fiddler抓包工具分析请求和响应的格式5.HTTP常见Header6.Cookie和Session的简单介绍三、补充1.HttpVSHttps提示:以下是本篇文章正文内容一、HTTP原理简介 HTTP协议(超文本传输协议HyperTextTransferProtocol Java Swing-2.环境搭建及窗口背景 crime_lamb javaswingjava开发语言 环境搭建及设置窗口背景环境搭建jdk:1.8Maven:3.2.5开发一个小型的桌面程序,可直接创建一个基础的Maven项目,我的场景中不使用Spring所以并没有使用Springboot框架下面是我的pom.xml4.0.0org.exampleSwing-Test1.0-SNAPSHOTArchetype-Swing-Testhttp://maven.apache.orgcom.formdev org.pentaho:pentaho-aggdesigner-algorithm:jar:5.1.5-jhyde Maven下载不下来 百夜﹍悠ゼ mavenjarjava 找了很多文章都提供以下两种方式1)设置maven镜像仓库aliyunmaven*阿里云spring插件仓库https://maven.aliyun.com/repository/spring-pluginnexus-aliyun*Nexusaliyunhttp://maven.aliyun.com/nexus/content/groups/public2)pom文件增加springhttps:// Springboot使用Thumbnailator压缩图片上传到阿里云OSS(无损压缩) 一勺菠萝丶 Java#OSS#SpringBoot 前提:图片的压缩大致有两种,一种是将图片的尺寸压缩小,另一种是尺寸不变,将压缩质量,一般对于项目我们需要第一种,即用户上传一张分辨率为3840 × 2160的图片,通过上传图片接口后上传到OSS上的图片分辨率会变成1920×1080(如3840 × 2160的图片大小为11.4M,上传后的图片大概会为1.9M),此时上传后到OSS的图片和原图质量上一致,也就是说看上去只的大小的区别,清晰度上没有任 关于旗正规则引擎中的MD5加密问题 何必如此 jspMD5规则加密 一般情况下,为了防止个人隐私的泄露,我们都会对用户登录密码进行加密,使数据库相应字段保存的是加密后的字符串,而非原始密码。 在旗正规则引擎中,通过外部调用,可以实现MD5的加密,具体步骤如下: 1.在对象库中选择外部调用,选择“com.flagleader.util.MD5”,在子选项中选择“com.flagleader.util.MD5.getMD5ofStr({arg1})”; 2.在规 【Spark101】Scala Promise/Future在Spark中的应用 bit1129 Promise Promise和Future是Scala用于异步调用并实现结果汇集的并发原语,Scala的Future同JUC里面的Future接口含义相同,Promise理解起来就有些绕。等有时间了再仔细的研究下Promise和Future的语义以及应用场景,具体参见Scala在线文档:http://docs.scala-lang.org/sips/completed/futures-promises.html spark sql 访问hive数据的配置详解 daizj spark sqlhivethriftserver spark sql 能够通过thriftserver 访问hive数据,默认spark编译的版本是不支持访问hive,因为hive依赖比较多,因此打的包中不包含hive和thriftserver,因此需要自己下载源码进行编译,将hive,thriftserver打包进去才能够访问,详细配置步骤如下: 1、下载源码 2、下载Maven,并配置 此配置简单,就略过 HTTP 协议通信 周凡杨 javahttpclienthttp通信 一:简介 HTTPCLIENT,通过JAVA基于HTTP协议进行点与点间的通信! 二: 代码举例 测试类: import java java unix时间戳转换 g21121 java 把java时间戳转换成unix时间戳: Timestamp appointTime=Timestamp.valueOf(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())) SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:m web报表工具FineReport常用函数的用法总结(报表函数) 老A不折腾 web报表finereport总结 说明:本次总结中,凡是以tableName或viewName作为参数因子的。函数在调用的时候均按照先从私有数据源中查找,然后再从公有数据源中查找的顺序。 CLASS CLASS(object):返回object对象的所属的类。 CNMONEY CNMONEY(number,unit)返回人民币大写。 number:需要转换的数值型的数。 unit:单位, java jni调用c++ 代码 报错 墙头上一根草 javaC++jni # # A fatal error has been detected by the Java Runtime Environment: # # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00000000777c3290, pid=5632, tid=6656 # # JRE version: Java(TM) SE Ru Spring中事件处理de小技巧 aijuans springSpring 教程Spring 实例Spring 入门Spring3 Spring 中提供一些Aware相关de接口,BeanFactoryAware、 ApplicationContextAware、ResourceLoaderAware、ServletContextAware等等,其中最常用到de匙ApplicationContextAware.实现ApplicationContextAwaredeBean,在Bean被初始后,将会被注入 Applicati linux shell ls脚本样例 annan211 linuxlinux ls源码linux 源码 #! /bin/sh - #查找输入文件的路径 #在查找路径下寻找一个或多个原始文件或文件模式 # 查找路径由特定的环境变量所定义 #标准输出所产生的结果 通常是查找路径下找到的每个文件的第一个实体的完整路径 # 或是filename :not found 的标准错误输出。 #如果文件没有找到 则退出码为0 #否则 即为找不到的文件个数 #语法 pathfind [-- List,Set,Map遍历方式 (收集的资源,值得看一下) 百合不是茶 listsetMap遍历方式 List特点:元素有放入顺序,元素可重复 Map特点:元素按键值对存储,无放入顺序 Set特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的) List接口有三个实现类:LinkedList,ArrayList,Vector LinkedList:底层基于链表实现,链表内存是散乱的,每一个元素存储本身 解决SimpleDateFormat的线程不安全问题的方法 bijian1013 javathread线程安全 在Java项目中,我们通常会自己写一个DateUtil类,处理日期和字符串的转换,如下所示: public class DateUtil01 { private SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public void format(Date d http请求测试实例(采用fastjson解析) bijian1013 http测试 在实际开发中,我们经常会去做http请求的开发,下面则是如何请求的单元测试小实例,仅供参考。 import java.util.HashMap; import java.util.Map; import org.apache.commons.httpclient.HttpClient; import 【RPC框架Hessian三】Hessian 异常处理 bit1129 hessian RPC异常处理概述 RPC异常处理指是,当客户端调用远端的服务,如果服务执行过程中发生异常,这个异常能否序列到客户端? 如果服务在执行过程中可能发生异常,那么在服务接口的声明中,就该声明该接口可能抛出的异常。 在Hessian中,服务器端发生异常,可以将异常信息从服务器端序列化到客户端,因为Exception本身是实现了Serializable的 【日志分析】日志分析工具 bit1129 日志分析 1. 网站日志实时分析工具 GoAccess http://www.vpsee.com/2014/02/a-real-time-web-log-analyzer-goaccess/ 2. 通过日志监控并收集 Java 应用程序性能数据(Perf4J) http://www.ibm.com/developerworks/cn/java/j-lo-logforperf/ 3.log.io 和 nginx优化加强战斗力及遇到的坑解决 ronin47 nginx 优化 先说遇到个坑,第一个是负载问题,这个问题与架构有关,由于我设计架构多了两层,结果导致会话负载只转向一个。解决这样的问题思路有两个:一是改变负载策略,二是更改架构设计。 由于采用动静分离部署,而nginx又设计了静态,结果客户端去读nginx静态,访问量上来,页面加载很慢。解决:二者留其一。最好是保留apache服务器。 来以下优化: java-50-输入两棵二叉树A和B,判断树B是不是A的子结构 bylijinnan java 思路来自: http://zhedahht.blog.163.com/blog/static/25411174201011445550396/ import ljn.help.*; public class HasSubtree { /**Q50. * 输入两棵二叉树A和B,判断树B是不是A的子结构。 例如,下图中的两棵树A和B,由于A中有一部分子树的结构和B是一 mongoDB 备份与恢复 开窍的石头 mongDB备份与恢复 Mongodb导出与导入 1: 导入/导出可以操作的是本地的mongodb服务器,也可以是远程的. 所以,都有如下通用选项: -h host 主机 --port port 端口 -u username 用户名 -p passwd 密码 2: mongoexport 导出json格式的文件 [网络与通讯]椭圆轨道计算的一些问题 comsci 网络 如果按照中国古代农历的历法,现在应该是某个季节的开始,但是由于农历历法是3000年前的天文观测数据,如果按照现在的天文学记录来进行修正的话,这个季节已经过去一段时间了。。。。。 也就是说,还要再等3000年。才有机会了,太阳系的行星的椭圆轨道受到外来天体的干扰,轨道次序发生了变 软件专利如何申请 cuiyadll 软件专利申请 软件技术可以申请软件著作权以保护软件源代码,也可以申请发明专利以保护软件流程中的步骤执行方式。专利保护的是软件解决问题的思想,而软件著作权保护的是软件代码(即软件思想的表达形式)。例如,离线传送文件,那发明专利保护是如何实现离线传送文件。基于相同的软件思想,但实现离线传送的程序代码有千千万万种,每种代码都可以享有各自的软件著作权。申请一个软件发明专利的代理费大概需要5000-8000申请发明专利可 Android学习笔记 darrenzhu android 1.启动一个AVD 2.命令行运行adb shell可连接到AVD,这也就是命令行客户端 3.如何启动一个程序 am start -n package name/.activityName am start -n com.example.helloworld/.MainActivity 启动Android设置工具的命令如下所示: # am start - apache虚拟机配置,本地多域名访问本地网站 dcj3sjt126com apache 现在假定你有两个目录,一个存在于 /htdocs/a,另一个存在于 /htdocs/b 。 现在你想要在本地测试的时候访问 www.freeman.com 对应的目录是 /xampp/htdocs/freeman ,访问 www.duchengjiu.com 对应的目录是 /htdocs/duchengjiu。 1、首先修改C盘WINDOWS\system32\drivers\etc目录下的 yii2 restful web服务[速率限制] dcj3sjt126com PHPyii2 速率限制 为防止滥用,你应该考虑增加速率限制到您的API。 例如,您可以限制每个用户的API的使用是在10分钟内最多100次的API调用。 如果一个用户同一个时间段内太多的请求被接收, 将返回响应状态代码 429 (这意味着过多的请求)。 要启用速率限制, [[yii\web\User::identityClass|user identity class]] 应该实现 [[yii\filter Hadoop2.5.2安装——单机模式 eksliang hadoophadoop单机部署 转载请出自出处:http://eksliang.iteye.com/blog/2185414 一、概述 Hadoop有三种模式 单机模式、伪分布模式和完全分布模式,这里先简单介绍单机模式 ,默认情况下,Hadoop被配置成一个非分布式模式,独立运行JAVA进程,适合开始做调试工作。 二、下载地址 Hadoop 网址http: LoadMoreListView+SwipeRefreshLayout(分页下拉)基本结构 gundumw100 android 一切为了快速迭代 import java.util.ArrayList; import org.json.JSONObject; import android.animation.ObjectAnimator; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayo 三道简单的前端HTML/CSS题目 ini htmlWeb前端css题目 使用CSS为多个网页进行相同风格的布局和外观设置时,为了方便对这些网页进行修改,最好使用( )。http://hovertree.com/shortanswer/bjae/7bd72acca3206862.htm 在HTML中加入<table style=”color:red; font-size:10pt”>,此为( )。http://hovertree.com/s overrided方法编译错误 kane_xie override 问题描述: 在实现类中的某一或某几个Override方法发生编译错误如下: Name clash: The method put(String) of type XXXServiceImpl has the same erasure as put(String) of type XXXService but does not override it 当去掉@Over Java中使用代理IP获取网址内容(防IP被封,做数据爬虫) mcj8089 免费代理IP代理IP数据爬虫JAVA设置代理IP爬虫封IP 推荐两个代理IP网站: 1. 全网代理IP:http://proxy.goubanjia.com/ 2. 敲代码免费IP:http://ip.qiaodm.com/ Java语言有两种方式使用代理IP访问网址并获取内容, 方式一,设置System系统属性 // 设置代理IP System.getProper Nodejs Express 报错之 listen EADDRINUSE qiaolevip 每天进步一点点学习永无止境nodejs纵观千象 当你启动 nodejs服务报错: >node app Express server listening on port 80 events.js:85 throw er; // Unhandled 'error' event ^ Error: listen EADDRINUSE at exports._errnoException ( C++中三种new的用法 _荆棘鸟_ C++new 转载自:http://news.ccidnet.com/art/32855/20100713/2114025_1.html 作者: mt 其一是new operator,也叫new表达式;其二是operator new,也叫new操作符。这两个英文名称起的也太绝了,很容易搞混,那就记中文名称吧。new表达式比较常见,也最常用,例如: string* ps = new string(" Ruby深入研究笔记1 wudixiaotie Ruby module是可以定义private方法的 module MTest def aaa puts "aaa" private_method end private def private_method puts "this is private_method" end end 按字母分类: ABCDEFGHIJKLMNOPQRSTUVWXYZ其他