SpringBoot应用

学习目标

  1. 学会SpringBoot的应用
  2. 了解SpringBoot的特性
  3. 了解SpringBoot的启动源码

知识要点

特性

特性1:依赖管理

springboot-boot-starter-parent的父pom是spring-boot-dependencies
在spring-boot-dependencies.pom里面包含了开发中常用的版本集合
如果使用默认版本,引入dependencies即可,如果需要自定义版本,那么需要在标签中引入自定义版本。
image.png

特性2:场景Starter

引入什么场景starter,那么就会将一整套场景的jar包都引入进来,我们也不需要关注多jar包直接的版本号是否兼容彼此,这些工作Spring已经帮我们做好了。
SpringBoot提供的场景Starter:
spring官方提供的starter:spring-boot-starter-*
自定义starter:thirdpartyproject-spring-boot-starter
分为三类:

  1. application starters:

如spring-boot-starter-web,spring-boot-starter-aop,spring-boot-starter-amqp

  1. production starters:

spring-boot-starter-actuator

  1. 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,进而进入了多个依赖。
image.png

特性3:自动配置AutoConfiguration

SpringBoot所有的自动配置功能都在spring-boot-autoconfiguration包里面,项目在启动时,会读取该文件,自动配置对应的类
image.png

特性4:默认包扫描路径

主程序MyApplication.java所在包及其下面所有子包里面的组件都会被默认扫描。
image.png

特性5:自定义包扫描路径

如果想要自定义扫描路径,可以进行以下配置
image.png

image.png

特性6:SpringBoot配置相关介绍

SpringBoot支持的两种配置类型:

  1. application.properties
  2. 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:自动配置原理

image.png
@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());
}
  1. 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);
}
  1. 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;
}
  1. 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;
}
  1. 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
image.png

  1. 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
image.png

特性10:按需开启自动配置项

在1.9中加载了配置文件中所有配置的类,但是并不是全部加载到了IOC当中。而是采用按需加载(即:@ConditionOnXXX)的方式进行加载。

  1. 容错兼容: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;
}
  1. 用户配置优先:WebMvcAutoConfiguration.defaultViewResolver()
  2. 外部配置项修改组件行为: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;
}
  1. 查看自动配置情况:在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);
}
  1. 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();
}
  1. 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;
	}

}
  1. 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;
}
  1. 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;
}
  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值