学习目标
- 学会SpringBoot的应用
- 了解SpringBoot的特性
- 了解SpringBoot的启动源码
知识要点
特性
特性1:依赖管理
springboot-boot-starter-parent的父pom是spring-boot-dependencies
在spring-boot-dependencies.pom里面包含了开发中常用的版本集合
如果使用默认版本,引入dependencies即可,如果需要自定义版本,那么需要在标签中引入自定义版本。
特性2:场景Starter
引入什么场景starter,那么就会将一整套场景的jar包都引入进来,我们也不需要关注多jar包直接的版本号是否兼容彼此,这些工作Spring已经帮我们做好了。
SpringBoot提供的场景Starter:
spring官方提供的starter:spring-boot-starter-*
自定义starter:thirdpartyproject-spring-boot-starter
分为三类:
- application starters:
如spring-boot-starter-web,spring-boot-starter-aop,spring-boot-starter-amqp
- production starters:
spring-boot-starter-actuator
- technical starters:
如:spring-boot-starter-jetty,spring-boot-starter-log4j2,spring-boot-starter-logging
详情见:https://docs.spring.io/spring-boot/docs/2.7.5/reference/pdf/spring-boot-reference.pdf 6.1.5
核心Starter:spring-boot-starter
从下图可以看出来,一个spring-boot-starter-web下引用了许多其他的starter,进而进入了多个依赖。
特性3:自动配置AutoConfiguration
SpringBoot所有的自动配置功能都在spring-boot-autoconfiguration包里面,项目在启动时,会读取该文件,自动配置对应的类
特性4:默认包扫描路径
主程序MyApplication.java所在包及其下面所有子包里面的组件都会被默认扫描。
特性5:自定义包扫描路径
如果想要自定义扫描路径,可以进行以下配置
或
特性6:SpringBoot配置相关介绍
SpringBoot支持的两种配置类型:
- application.properties
- application.yaml / application.yml
自定义配置:
使用以下两种方式添加注解后即可在配置文件中配置相关值
@Component
@ConfigurationProperties(prefix = "muse")
@Data
public class Muse {
private String name;
private int age;
}
或
@ConfigurationProperties(prefix = "muse")
@Data
public class Muse {
private String name;
private int age;
}
@EnableConfigurationProperties(Muse.class) // 测试引入自定义配置Muse
@Configuration
public class DemoConfig {
}
添加以下依赖后,在配置文件中添加配置时可以像配置官方配置一样拥有提示
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
特性7:yaml书写规则
student:
name: muse
sex: true
birth: 2021/06/05
age: 20
address:
street: 王府井大街
city: 北京市
interests: [a,b,c,d]
friends:
- lilei
- hanmeimie
score: {math: 100, english: 80}
weight-his:
- 1111
- 333
- 4444
- 5555
all-friends-address:
bestFriendAddress:
- {street: 中关村大街,city: 北京市}
- street: 上海某大街
city: 上海市
worstFriendAddress:
- {street: 深圳某大街,city: 深圳市}
- {street: 成都某大街,city: 成都市}
@ConfigurationProperties(prefix = "student")
@Component
@Data
public class Student {
private String name;
private Boolean sex;
private Date birth;
private Integer age;
private Address address;
private String[] interests;
private List<String> friends;
private Map<String, Double> score;
private Set<Double> weightHis;
private Map<String, List<Address>> allFriendsAddress;
}
特性8:常用注解
注解 | 说明 |
---|---|
@Configuration | 定义配置类,之前的Spring配置都是写在xml配置文件里面的。在新的Spring版本中,建议首要选择把配置文件写在配置类中。 |
@ComponentScan | 定义扫描路径 |
@Bean | 默认方法名就是id,返回类型就是方法返回的类型。也可以@Bean(“xxx”),指定Bean的名称 |
@Import | 给容器中自动创建出注解中指定类型的组件,默认组件的名字就是全类名。 |
@Conditional | 满足Conditional指定的条件时,才向IOC容器注入组件 |
@ImportResource | 我们指定对应的xml文件,spring就可以把xml中配置的Bean都加载到IOC中,而不用我们一个个的手写@Bean了 , @ImportResource({“classpath:oldbean.xml”}):指定xml文件 |
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootDemoApplication.class, args);
/** 获得所有beanName列表 */
// Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
/** 获得自定义配置文件Muse的值 */
Muse muse = context.getBean(Muse.class);
System.out.println("muse = " + muse);
/** 测试@Bean注解 */
Gun gun = context.getBean("gun", Gun.class);
System.out.println("gun = " + gun);
gun = context.getBean("ak47", Gun.class);
System.out.println("gun = " + gun);
/** 测试嵌套赋值 */
Soldier soldier = context.getBean("soldier", Soldier.class);
System.out.println("soldier = " + soldier);
/** 测试每次调用gun()是否都是重新创建对象 */
DemoConfig demoConfig = context.getBean(DemoConfig.class);
Gun gun1 = demoConfig.gun();
Gun gun2 = demoConfig.gun();
Gun gun3 = demoConfig.gun();
System.out.println("gun1 == gun2 is " + (gun1 == gun2));
System.out.println("gun1 == gun3 is " + (gun1 == gun3));
System.out.println("gun2 == gun3 is " + (gun2 == gun3));
/** 测试@Import */
boolean exists = context.containsBean("com.muse.springbootdemo.entity.Gun");
System.out.println("@Import gun exists = " + exists);
exists = context.containsBean("com.muse.springbootdemo.entity.Soldier");
System.out.println("@Import soldier exists = " + exists);
/** 测试@ConditionalOnBean */
exists = context.containsBean("soldier4War");
System.out.println("soldier4War exists = " + exists);
/** 测试@Import */
exists = context.containsBean("awm");
System.out.println("awm exists = " + exists);
}
}
// 当m416这个bean存在,再创建这个bean
@Bean
@ConditionalOnBean(name = "m416")
public Soldier soldier4War() {
Soldier soldier = new Soldier();
soldier.setName("muse");
soldier.setAge(20);
soldier.setGun(gun2()); // 将IOC中的Gun实例赋值给Soldier
return soldier;
}
特性9:自动配置原理
@Import(AutoConfigurationPackages.Registrar.class)
确定包扫描路径,以传入启动类,解析启动类的package
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Import(AutoConfigurationImportSelector.class)
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 获得自动配置的实体 ,解析见1
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
// 封装成字符串数组
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
- getAutoConfigurationEntry(annotationMetadata);解析
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获得Configurations配置(核心代码),解析见2
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 移除掉重复的
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查是否需要排除掉的
checkExcludedClasses(configurations, exclusions);
// 移除需要排除掉的
configurations.removeAll(exclusions);
// 过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 封装
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
- getCandidateConfigurations(annotationMetadata, attributes);解析
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 获取一部分配置,解析见3
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
// 再次获取一部分配置添加至configurations中,解析见5
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
- SpringFactoriesLoader.loadFactoryNames();解析
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
// 传入的classLoader为空,使用当前类的classLoader
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// EnableAutoConfiguration
String factoryTypeName = factoryType.getName();
// factoryTypeName是否存在于4中返回的数据中,如果存在直接返回,不存在返回空集合
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
// factoryType就是2中传入的getSpringFactoriesLoaderFactoryClass(),就是获取到注解EnableAutoConfiguration的class
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
- loadSpringFactories();解析
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 从缓存中获取
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// FACTORIES_RESOURCE_LOCATION->"META-INF/spring.factories"
// 进行加载操作,加载路径->META-INF/spring.factories
// 查看META-INF/spring.factories文件,发现是key-value形式,并且和cache的value格式一样
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
// 通过循环,进行加载
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// properties就是一个文件中加载完成的数据,properties中value值依然是逗号分隔的
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 通过循环遍历,处理properties,把数据放入result
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 获取key
String factoryTypeName = ((String) entry.getKey()).trim();
// 处理value,变为数组
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
// 将数据放入result中
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
// 加入缓存
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
META-INF/spring.factories
- ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader());解析
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
// 判空
Assert.notNull(annotation, "'annotation' must not be null");
// classLoader判空,如果为空获取当前类的classLoader
ClassLoader classLoaderToUse = decideClassloader(classLoader);
// LOCATION->"META-INF/spring/%s.imports"
// 从"META-INF/spring/%s.imports"中加载
String location = String.format(LOCATION, annotation.getName());
// 进行加载
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> autoConfigurations = new ArrayList<>();
// 遍历,将值放入autoConfigurations中
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
autoConfigurations.addAll(readAutoConfigurations(url));
}
return new ImportCandidates(autoConfigurations);
}
// findUrlsInClasspath
private static Enumeration<URL> findUrlsInClasspath(ClassLoader classLoader, String location) {
try {
return classLoader.getResources(location);
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to load autoconfigurations from location [" + location + "]",
ex);
}
}
META-INF/spring/%s.imports
特性10:按需开启自动配置项
在1.9中加载了配置文件中所有配置的类,但是并不是全部加载到了IOC当中。而是采用按需加载(即:@ConditionOnXXX)的方式进行加载。
- 容错兼容:DispatcherServletAutoConfiguration.multipartResolver(…)
@Bean
// IOC中已经存在MultipartResolver的Bean
@ConditionalOnBean(MultipartResolver.class)
// 没有multipartResolver这个BeanName,直接帮助改掉名字
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// 用户创建了MultipartResolver,但是名字是错误的
return resolver;
}
- 用户配置优先:WebMvcAutoConfiguration.defaultViewResolver()
- 外部配置项修改组件行为:WebMvcAutoConfiguration.defaultViewResolver()
通过在application.yaml中配置spring.mvc.view.prefix和spring.mvc.view.suffix就可以让获取到不同的值
@Bean
// 如果没有defaultViewResolver这个bean,则帮助创建
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
return resolver;
}
- 查看自动配置情况:在application.yaml中配置debug=true,启动时就可以看到相关配置
SpringBoot Web
静态资源访问
默认静态资源目录/static、/public、/resource、/META-INF/resource,可以通过项目根路径+静态资源名访问访问,http://localhost:8080/xxx.jpg
自定义访问路径,配置spring.mvc.static-path-pattern=/resource/**,即:http://localhost:8080/resource/xxx.jpg
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 可配置为禁用静态资源
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
// 可通过spring.mvc.static-path-pattern进行配置
// this.resourceProperties.getStaticLocations()中就是{ "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" }
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
REST风格源码解析
使用需开启
# 开启hiddenMethod过滤
spring:
mvc:
hiddenmethod:
filter:
enabled: true
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
// 必须是post请求类型,并且没有错误
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
// methodParam=_method,paramValue="DELETE"
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
// ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
自定义入参
/** 自定义解析 */
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Teacher>() {
@Override
public Teacher convert(String source) {
if (!StringUtils.hasText(source)) {
return null;
}
String[] sourceArgs = source.split(",");
Teacher teacher = new Teacher();
teacher.setName(sourceArgs[0]);
teacher.setAge(Integer.valueOf(sourceArgs[1]));
teacher.setSex(Integer.valueOf(sourceArgs[2]));
return teacher;
}
});
}
};
}
SpringBoot启动
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// new SpringApplication(primarySources)见1,run(args)中主要代码为启动spring
return new SpringApplication(primarySources).run(args);
}
- new SpringApplication(primarySources)
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// resourceLoader为null
this.resourceLoader = resourceLoader;
// primarySources为启动类
Assert.notNull(primarySources, "PrimarySources must not be null");
// 启动类封装为LinkedHashSet
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 解析见2
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 下面方法都是讲数据封装到arraylist中
// getSpringFactoriesInstances解析见3
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 见5
this.mainApplicationClass = deduceMainApplicationClass();
}
- WebApplicationType.deduceFromClasspath()
public enum WebApplicationType {
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
static WebApplicationType deduceFromClasspath() {
// 满足以下条件,为reactive技术栈
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
// 不存在SERVLET_INDICATOR_CLASSES中的,直接返回NONE
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 走servlet技术栈
return WebApplicationType.SERVLET;
}
}
- getSpringFactoriesInstances
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 见1.9特性9中的解析3
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 创建SpringFactories实例,见4
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
4.createSpringFactoriesInstances
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
// 通过反射获取示例对象
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
- deduceMainApplicationClass()
private Class<?> deduceMainApplicationClass() {
try {
// 堆栈
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 循环找到实际的启动方法
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}