SpringBootܻ原理深入及源码剖析 依赖管理 问题一:为什么某些Maven依赖不需要指定版本号? 因为spring-boot-starter-parent中已经定义了常用依赖匹配的版本号.
按住Ctrl,点击spring-boot-starter-parent的版本号
可见pom文件中的parent是spring-boot-dependencies,然后再次按住Ctrl,点击版本号
我们会发现这个pom中维护了一大堆依赖的版本号
问题二:Springboot是如何引入这些jar包的 在我们做web项目时,除了上面的parent外,还会引入spring-boot-starter-web,而在这个依赖中,则定义了web项目需要的jar依赖.
依据不同的使用场景,spring官方也提供了不同的starter封装
问题三:Springboot是如何进行自动配置的 我们进入查看启动类上的SpringBootApplication注解
@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 {}
1.@SpringBootConfiguration 声明当前类为配置类
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods () default true ; }
2.@EnableAutoConfiguration 表示开启自动配置功能,也是实现自动化配置的注解
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration" ; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
@AutoConfigurationPackage
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({AutoConfigurationPackages.Registrar.class}) public @interface AutoConfigurationPackage { String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; }
我们继续查看Registrar中的实现
static class Registrar implements ImportBeanDefinitionRegistrar , DeterminableImports { Registrar() { } public void registerBeanDefinitions (AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (String[])(new PackageImports (metadata)).getPackageNames().toArray(new String [0 ])); } public Set<Object> determineImports (AnnotationMetadata metadata) { return Collections.singleton(new PackageImports (metadata)); } }
可以发现其中有一个registerBeanDefinitions方法,就是将主程序类所在包及所有子包下的组件扫描的Spring容器中
@Import({AutoConfigurationImportSelector.class})
将AutoConfigurationImportSelector类导入到容器中,这个类可以将所有符合条件的@Configuration注解类配置加载到Spring的IOC容器中.
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this .isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationEntry autoConfigurationEntry = this .getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } }
深入loadMetadata方法
static AutoConfigurationMetadata loadMetadata (ClassLoader classLoader) { return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties" ); } static AutoConfigurationMetadata loadMetadata (ClassLoader classLoader, String path) { try { Enumeration<URL> urls = classLoader != null ? classLoader.getResources(path) : ClassLoader.getSystemResources(path); Properties properties = new Properties (); while (urls.hasMoreElements()) { properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource ((URL)urls.nextElement()))); } return loadMetadata(properties); } catch (IOException var4) { throw new IllegalArgumentException ("Unable to load @ConditionalOnClass location [" + path + "]" , var4); } }
深入getCandidateConfigurations方法
protected List<String> getCandidateConfigurations (AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> 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; } protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } protected ClassLoader getBeanClassLoader () { return this .beanClassLoader; }
继续点开loadFactoryNames方法
public static List<String> loadFactoryNames (Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories (@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null ) { return result; } else { try { Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories" ) : ClassLoader.getSystemResources("META-INF/spring.factories" ); MultiValueMap<String, String> result = new LinkedMultiValueMap (); 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()) { Map.Entry<?, ?> entry = (Map.Entry)var6.next(); String factoryClassName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for (int var11 = 0 ; var11 < var10; ++var11) { String factoryName = var9[var11]; result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException ("Unable to load factories from location [META-INF/spring.factories]" , var13); } } }
实际上,加载的就是这个路径下的配置文件
以web项目为例,在项目中加入了Web环境依赖启动器,对应的WebMvcAutoConfiguration就会生效,打开这个配置类,就会发现该类通过全注解的方式对Spring所需环境进行了默认配置.
总结
springboot底层自动配置的步骤是:
1.springboot应用启动
2.@SpringBootApplication起作用
3.@EnableAutoConfiguration
4.@AutoConfigurationPackage:扫描加载启动类同级及子级目录下的配置类
5.@Import(AutoConfigurationImportSelector.class):加载classpath上jar中的META-INF/spring.factories文件中指定的配置类
3.@ComponentScan 扫描启动类下面的bean对象
总结 |- @SpringBootConfiguration |- @Configuration |- @EnableAutoConfiguration |- @AutoConfigurationPackage |- @Import(AutoConfigurationImportSelector.class) |- @ComponentScan
自定义Starter 1.引入自动配置依赖 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-autoconfigure</artifactId > <version > 2.2.2.RELEASE</version > </dependency >
2.定义JavaBean @EnableConfigurationProperties(SimpleBean.class) @ConfigurationProperties(prefix = "simple") @Data public class SimpleBean { private int id; private String name; }
3.编写配置类 @Configuration public class MyAutoConfiguration { static { System.out.println("MyAutoConfiguration init...." ); } @Bean public SimpleBean simpleBean () { return new SimpleBean (); } }
4.配置启动类加载路径 在resources下创建/META-INF/spring.factories文件,指定配置类全路径
5.测试starter 在测试包中引入自定义starter
<dependency > <groupId > org.example</groupId > <artifactId > my-spring-boot-starter</artifactId > <version > 1.0-SNAPSHOT</version > </dependency >
在配置文件application.properties中加入前缀配置
simple.id=1 simple.name=test
编写测试方法
@Autowired private SimpleBean simpleBean;@Test public void testStarter () { System.out.println(simpleBean); }
执行原理 我们从启动类开始断点查看程序执行步骤,首先是调用了SpringApplication的run方法.
在run方法中,主要做了两个操作,一个是SpringApplication的初始化,以及调用run()启动项目
(new SpringApplication (primarySources)).run(args);
1.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 = new HashSet (); this .isCustomEnvironment = false ; this .lazyInitialization = false ; this .resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null" ); this .primarySources = new LinkedHashSet (Arrays.asList(primarySources)); this .webApplicationType = WebApplicationType.deduceFromClasspath(); this .setInitializers(this .getSpringFactoriesInstances(ApplicationContextInitializer.class)); this .setListeners(this .getSpringFactoriesInstances(ApplicationListener.class)); this .mainApplicationClass = this .deduceMainApplicationClass(); }
2.run()方法执行 public ConfigurableApplicationContext run (String... args) { StopWatch stopWatch = new StopWatch (); stopWatch.start(); ConfigurableApplicationContext context = null ; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList (); this .configureHeadlessProperty(); SpringApplicationRunListeners listeners = this .getRunListeners(args); listeners.starting(); Collection exceptionReporters; try { ApplicationArguments applicationArguments = new DefaultApplicationArguments (args); ConfigurableEnvironment environment = this .prepareEnvironment(listeners, applicationArguments); this .configureIgnoreBeanInfo(environment); Banner printedBanner = this .printBanner(environment); context = this .createApplicationContext(); exceptionReporters = this .getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class []{ConfigurableApplicationContext.class}, context); this .prepareContext(context, environment, listeners, applicationArguments, printedBanner); this .refreshContext(context); 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, exceptionReporters, listeners); throw new IllegalStateException (var10); } try { listeners.running(context); return context; } catch (Throwable var9) { this .handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null ); throw new IllegalStateException (var9); } }
Springboot缓存管理 默认缓存 1.启动类使用@EnableCaching注解
@SpringBootApplication @EnableCaching public class Springboot01DemoApplication { public static void main (String[] args) { SpringApplication.run(Springboot01DemoApplication.class, args); } }
2.查询方法添加@Cacheable注解配置缓存
@Cacheable(cacheNames = "a") public A findById (String id) { A a = dao.findById(id) return a; }
springboot默认使用的是SimpleCacheConfiguration配置类,用CurrentMap当作底层数据结构进行数据缓存
整合Redis进行缓存 1.添加Spring Data Redis依赖启动器 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
当我们加入此依赖后,Springboot会使用RedisCacheConfiguration当作生效的自动配置类,容器使用的缓存管理器是RedisCacheManager类,创建的Cache是RedisCache
2.添加redis连接配置 spring.redis.host =127.0.0.1 spring.redis.port =6379 spring.redis.password =
3.将缓存对象进行序列化 对缓存的实体类实现Serializable,添加序列化ID
4.使用上面同样的注解,即可实现基于Redis的缓存