java—07 springboot介绍

springboot官网:Spring | HomeLevel up your Java code and explore what Spring can do for you.https://spring.io/

开发手册:

开发手册:Spring Boot Reference Documentationhttps://docs.spring.io/spring-boot/docs/2.7.18/reference/htmlsingle/#getting-started.first-application

springboot特点:

特点说明
创建独立的Spring应用SpringBoot可以创建独立的Spring应用,它比用原生的SpringFramework开发的 应用更简单,配置更少。
内嵌web服务器以前开发web应用,会把项目打成war包,然后部署到外部Tomcat运行项目,现 在直接运行jar包即可。
提供可选的start依赖, 简化构建配置启动器start,可以直接引入该场景下所有的包依赖,并且多个jar包对应的版本 也帮我们选择好了。
自动配置Spring以及第 三方功能以前开发Spring项目,有很多常规配置需要配置,并且引入其他技术时,都伴随 着大量的配置需要手动执行。那么有了自动配置后,这些都不需要自己去配置了, 可以直接面向业务代码开发,而不必被大量配置所困扰了。
提供生产级别的特性SpringBoot自带了生产级别的指标和运行状况检查,可以帮助我们了解服务运行 的最新状况。并且,当我们需要修改某些配置的时候,也不需要直接在项目源码 上进行修改了,可以通过外部化配置,就可以将修改生效。
完全不需要代码生成, 也不需要XML配置SpringBoot是整合Spring生态圈技术栈的一站式框架,是简化Spring技术栈的快 速开发脚手架,缺点是SpringBoot迭代快,变化快,且封装很深,内部原理复杂, 不容易精通。

新建springboot项目

加入parent:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

加入依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

写主启动类:

@RestController    // 即 @Controller 和 @ResponseBody
@SpringBootApplication
public class MyApplication {

    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}

安装ApiPost插件,在IDE中测试(替代postman)

(首先启动服务)

也通过 https://start.spring.io/ 帮助搭建项目脚手架:

springboot特性1:属性管理

spring-boot-starter-parent的父pom是spring-boot-dependencies

在spring-boot-dependencies.pom里面包含了开发中常用的版本集合。

如果我们只是使用默认的版本,那么引入dependency即可;但是如果我们需要自定义依 赖版本,那么额外还需要在标签中引入自定义的版本。

特性2:场景starter

我们引入什么场景的starter,那么就会将一整套场景的jar包都引入进来,我们也不需要关 注多jar包直接的版本号是否兼容彼此,这块工作spring已经帮我们做好了。

SpringBoot提供的Starter有哪些:Build Systems :: Spring Boothttps://docs.spring.io/spring-boot/reference/using/build-systems.html#using.build-systems.starters分为三类Starter,分别为:

application starters

production starters

technical starters

核心starter:

特性3:自动配置AutoConfiguration

SpringBoot所有的自动配置功能都在spring-boot-autoconfigure包里面。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

特性4:默认包扫描路径

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

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

如果有的包或者文件跟主启动类不在一个层级下,如果想要被扫描到,可以指定如下注解: @SpringBootApplication(scanBasePackages = "com.demo")

或者指定如下注解: @ComponentScan("com.demo")

建议使用默认包扫描路径即可

特性6:SpringBoot配置相关介绍

SpringBoot支持两种配置类型:(加载的优先级:.properties  .ymal  .yml)

① application.properties

② application.yaml / application.yml

介绍properties和yaml的配置项书写方式对比,以及介绍默认配置值和配置对应处理类

自定义配置绑定方式

方式1:@ConfigurationProperties(prefix = "demo") + @Component  这两个注解放在实体类上

方式2:@ConfigurationProperties + @EnableConfigurationProperties

方式1举例:

实体类:

@Component
@ConfigurationProperties(prefix = "wlp")
@Data
public class Student {
    String name;
    int age;
}

配置文件application.properties:

wlp.name=xxxx
wlp.age=12

主启动类main方法:(主启动类上加了@RestController注解)

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringboottestApplication.class, args);
        Student student = context.getBean(Student.class);
        System.out.println(student);
    }

运行结果:

方式2举例:

配置类:@EnableConfigurationProperties注解的参数可以是class数组

@Configuration
@EnableConfigurationProperties(Student.class)
    public class config {
}

实体类:

@ConfigurationProperties(prefix = "wlp")
@Data
public class Student {
    String name;
    int age;
}

配置文件:

wlp.name=xxxx
wlp.age=12

主启动类:

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringboottestApplication.class, args);
        Student student = context.getBean(Student.class);
        System.out.println(student);
    }

在application.properties中给我们自定义的属性赋值时,不会提示,如果需要提示,需要添加依赖

添加配置提醒:(添加完依赖后,刷新Maven,同时Rebuild Project

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

特性7:yaml书写规则

(实体类➕@ConfigurationProperties(prefix = "student")

配置类➕@EnableConfigurationProperties(Student.class))

特性8:常用注解:

注解说明
@Configuration

定义配置类,之前的Spring配置都是写在xml配置文件里的。在新的Spring版本中,首要选择把配置写在配置类中。

@ComponentScan定义扫描路径
@Bean默认方法名就是bean的id,返回类型就是方法返回的类型。也可@Bean("xxx"),指定bean的名称。
@Import给容器中自动创建出注解中指定类型的组件,默认组件的名字就是全类名。
@Conditional满足Conditional指定的条件时,才向IOC容器中注入组件。
@ImportResource制定对应的xml文件,Spring就可以把xml中配置的Bean都加载到IOC中,而不用一个个手写@Bean了。

@Bean示例:

实体类Address:

@Data
public class Address {
    private int id;
    private String province;
    private String city;
    private String district;
}

配置类:

@Configuration
public class config {
    @Bean("northCity")
    public Address north(){
        Address address = new Address();
        address.setId(2);
        address.setProvince("河北");
        address.setCity("保定");
        address.setDistrict("莲池区");
        return address;
    }
}

主启动类:

//        SpringboottestApplication.class 为主启动类的名字,northCity为bean的名字(@Bean的属性值优先级高于方法名)
        ConfigurableApplicationContext context = SpringApplication.run(SpringboottestApplication.class, args);
        Address north = context.getBean("northCity", Address.class);
        System.out.println(north);

运行结果:

获得全部bean的名字:

        ConfigurableApplicationContext context = SpringApplication.run(SpringboottestApplication.class, args);
        Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);

@Import   8:30

@Conditional示例:(只有存在“northCity”这个bean的时候,才会创建“newStudent”这个bean)

@Configuration
public class config {
    @Bean("northCity")
    public Address north(){
        Address address = new Address();
        address.setId(2);
        address.setProvince("河北");
        address.setCity("保定");
        address.setDistrict("莲池区");
        return address;
    }

    @Bean("newStudent")
    @ConditionalOnBean(name = "northCity")
    public Student student(){
        Student student = new Student();
        student.setAge(11);
        student.setName("hahaha");
        student.setAddress(north());
        return student;
    }
}

注释掉 @Bean("northCity") 时,以下输出两个false,不注释掉的话,输出两个true。 

ConfigurableApplicationContext context = SpringApplication.run(SpringboottestApplication.class, args);
boolean newStudent = context.containsBean("newStudent");
boolean northCity = context.containsBean("northCity");
System.out.println(newStudent);
System.out.println(northCity);

@ImportResource 示例:

(使用场景:项目中有一些老资源是用xml文件写的,当想用xml文件中的bean时,可以用@ImportResource引入)

resource路径下写student.xml文件:

<bean id="importStudent" class="com.wlp.springboottest.entity.Student">
	<property name="name" value="conger"></property>
	<property name="age" value="19"></property>
</bean>

配置类config上加注解:

@ImportResource("classpath:student.xml")

以下测试语句输出 true:

boolean importStudent = context.containsBean("importStudent");
System.out.println(importStudent);

特性9:自动配置原理

(面)SpringBoot里面的注解@SpringBootApplication 做了什么?回答一下三大点:

@SpringBootApplication 

        @SpringBootConfiguration:配置类的属性

        @ComponentScan:自定义扫描路径

        @EnableAutoConfiguration:

                    @Import(AutoConfigurationImportSelector.class)

                    @AutoConfigurationPackage

                                @Import(AutoConfigurationPackages.Registrar.class):确定包扫描路径(new PackageImports(metadata).getPackageNames().toArray(new String[0]))(以传入启动类,然后解析启动类的package获得的,所以只能识别到启动类同级及子集目录,上级识别不到)

                   

1. 进入AutoConfigurationPackages的Registrar类:

	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        // metadata 是主启动类名,new PackageImports(metadata).getPackageNames().toArray(new String[0]) 是主启动类的路径,即确定包扫描路径
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

register中的metadata就是主启动类 

2. 进入AutoConfigurationImportSelector类:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

getAutoConfigurationEntry(AnnotationMetadata annotationMetadata):

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	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);
}

对获得的configurations进行重复的移除。

getCandidateConfigurations(annotationMetadata, attributes):主要是从META-INF/spring.factories和META-INF/spring/%s.imports中加载配置信息。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	List<String> configurations = new ArrayList<>(
			SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
	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;
}

3行的load和4行的import所对应的加载的内容是绿色部分的两个路径:META-INF/spring.factories 和 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.

(1)loadFactoryNames() :

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

loadFactoryNames() 的参数:getSpringFactoriesLoaderFactoryClass()

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
	return EnableAutoConfiguration.class;
}

 所以 factoryTypeName 为 EnableAutoConfiguration。

loadSpringFactories(ClassLoader classLoader):将META-INF/spring.factories路径下的所有的配置文件的键值对放到缓存中。key是接口,value是实现类。 

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
	Map<String, List<String>> result = (Map)cache.get(classLoader);  // 缓存中获得值,默认没有
	if (result != null) {
		return result;
	} else {
		Map<String, List<String>> result = new HashMap();

		try {
			Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");

			while(urls.hasMoreElements()) {
				URL url = (URL)urls.nextElement();
				UrlResource resource = new UrlResource(url);   // url:spring-boot-2.7.5.jar!/META-INF/spring.factories
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				Iterator var6 = properties.entrySet().iterator();

				while(var6.hasNext()) {
					Map.Entry<?, ?> entry = (Map.Entry)var6.next();
					String factoryTypeName = ((String)entry.getKey()).trim();
					String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
					String[] var10 = factoryImplementationNames;
					int var11 = factoryImplementationNames.length;
// result 即 spring.factories中的键值对
					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) {
			IOException ex = var14;
			throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", ex);
		}
	}
}

"META-INF/spring.factories" :以下factories的格式即上面定义的result(cache)的类型

以上的urls指代的三个路径是:

META-INF/spring.factories

Maven/apache-maven-3.8.6/maven-repo/org/springframework/boot/spring-boot-autoconfigure/2.7.5/spring-boot-autoconfigure-2.7.5.jar!/META-INF/spring.factories

Maven/apache-maven-3.8.6/maven-repo/org/springframework/spring-beans/5.3.23/spring-beans-5.3.23.jar!/META-INF/spring.factories

回到AutoConfigurationImportSelector类:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	List<String> configurations = new ArrayList<>(
			SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
	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;
}

configurations为空,原因:SpringFactoriesLoader.loadFactoryNames加载出来的键值对列表中需要返回factoryTypeName 为 EnableAutoConfiguration类型的键值对,没有该键值对则configurations为空。

(2)ImportCandidates.load方法:从META-INF/spring/%s.imports里面加载信息放到configurations中。

    public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
        Assert.notNull(annotation, "'annotation' must not be null");
        ClassLoader classLoaderToUse = decideClassloader(classLoader);
        String location = String.format("META-INF/spring/%s.imports", annotation.getName());
        Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
        List<String> autoConfigurations = new ArrayList();

        while(urls.hasMoreElements()) {
            URL url = (URL)urls.nextElement();
            autoConfigurations.addAll(readAutoConfigurations(url));
        }

        return new ImportCandidates(autoConfigurations);
    }

所以进行加载操作的加载路径包括:META-INF/spring.factories  和  META-INF/spring/%s.imports

META-INF/spring.factories包括:

findUrlsInClasspath(ClassLoader classLoader, String location) 进行加载:

    private static Enumeration<URL> findUrlsInClasspath(ClassLoader classLoader, String location) {
        try {
            return classLoader.getResources(location);
        } catch (IOException var3) {
            IOException ex = var3;
            throw new IllegalArgumentException("Failed to load autoconfigurations from location [" + location + "]", ex);
        }
    }

把加载的内容放入 autoConfigurations 中,然后再添加到add中:

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = new ArrayList<>(
				SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
		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;
	}

此时的configurations是全部的AutoConfiguration.

以上步骤说明了从两个路径(META-INF/spring.factories  和  META-INF/spring/%s.imports)去加载spring启动时候需要的一些类,最终加载出了144个AutoConfiguration。

@SpringBootApplication主要做了一下内容:

  • ① 负责配置
  • ② 负责包扫描
  • ③ 负责从默认包扫描路径下以及selector的两个路径下(META-INF/spring.factories  和  META-INF/spring/%s.imports)加载配置,第一个路径

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

如上所述,虽然加载了spring.factories配置文件中所有配置的类,但是,并不是全部都加载到IOC中,而是采用按需加载(即:@ConditionOnXXX)的方式进行加载。这种方式可以归纳为:

  • ① 容错兼容:DispatcherServletAutoConfiguration.multipartResolver(…)
  • ② 用户配置优先:WebMvcAutoConfiguration.defaultViewResolver()
  • ③ 外部配置项修改组件行为:WebMvcAutoConfiguration.defaultViewResolver()
  • ④ 查看自动配置情况:debug=true (在application.yml中配置,在启动的时候可以看到配置情况)

容错兼容:

@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
	// Detect if the user has created a MultipartResolver but named it incorrectly
	return resolver;
}
 public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";

存在MultipartResolver的bean,但是名字不叫multipartResolver的情况下创建multipartResolver 的 bean

以上用法主要是为了改名字:用户创建了resolver但是名字起错了,以上方法将用户创建的resolver以“multipartResolver”为名字返回

用户配置优先、外部配置项修改组件行为:

@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
	InternalResourceViewResolver resolver = new InternalResourceViewResolver();
	resolver.setPrefix(this.mvcProperties.getView().getPrefix());
	resolver.setSuffix(this.mvcProperties.getView().getSuffix());
	return resolver;
}

用户创建了的话,用用户创建的,用户没有创建的话,spring帮忙创建。

外部配置项修改组件行为:找到配置项的路径,在yaml中重新给赋值(如下图),直接就在以上resolver.setPrefix()中生效了。

查看自动配置

:yaml中配置debug=true之后重新启动,会看到有些自动配置没有匹配到,因为这个配置类中在按条件创建bean的时候缺少类

SpringBoot Web:

静态资源访问:

请求进来,先看Controller能不能处理,如果不能处理,尝试去寻找静态资源。可以通过【当前项目根路径+静态资源名】即:http://localhost:8080/kangxi.jpg的方式,访问静态资源

默认情况下,springboot server的静态资源在以下路径可以被直接请求到:

/static

/public

/resources

/META-INF.resources

如果想放到自己创建的目录下,且能够被识别到,需要在application.yml中进行配置:

spring:
  web:
    resources:
      static-locations: [classpath:/test0821/]

其中【test0821】是自己在resources下创建的目录。

另一种配置:

spring:
  mvc:
    static-path-pattern: "/resources/**"

此种配置时的访问路径:http://localhost:8080/resources/3.jpg

相关源码在WebMvcAutoConfiguration.addResourceHandlers(...)方法中

isAddMapping()默认为true,所以如果在yaml中不配置add-mapping的话,会继续往下走,获取静态资源目录,如果配置了为false,即为将静态资源禁止了,会直接返回,去读取自己配置的路径。

Rest风格请求映射:

请求路径:采用@RequestMapping 或 @XxxMapping

Rest风格支持(使用HTTP请求方式动词啦表示对资源的操作)

举例:

在 /static  /public   /resources 任一路径下创建html页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
get方法测试:
<form action="/student" method="get">
    <input value="提交" type="submit">
</form>
post方法测试:
<form action="/student" method="post">
    <input value="提交" type="submit">
</form>

delete方法测试:
<form action="/student" method="delete">
    <input value="提交" type="submit">
</form>
put方法测试:
<form action="/student" method="put">
    <input value="提交" type="submit">
</form>

</body>
</html>

controller类:

@RestController
public class StudentController {
    @GetMapping("/student")
    public String getStudent(){
        return "getStudent";
    }
    @PostMapping("/student")
    public String postStudent(){
        return "postStudent";
    }
    @DeleteMapping("/student")
    public String deleteStudent(){
        return "deleteStudent";
    }
    @PutMapping("/student")
    public String putStudent(){
        return "putStudent";
    }
}

网页端请求:http://localhost:8080/student.html

此时只有get和post能够请求到,delete和put请求到的都是get方法。

解决此问题:

携带_method的表单提交,application.yml中开启hiddenMethod过滤:

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

修改前端请求代码:

delete方法测试:
<form action="/student" method="post">
    <input name="_method" type="hidden" value="DELETE">
    <input value="提交" type="submit">
</form>
put方法测试:
<form action="/student" method="post">
    <input name="_method" type="hidden" value="PUT">
    <input value="提交" type="submit">
</form>

按住Ctrl点击 spring.mvc.hiddenmethod.filter.enabled: true 进入:

{
      "name": "spring.mvc.hiddenmethod.filter.enabled",
      "type": "java.lang.Boolean",
      "description": "Whether to enable Spring's HiddenHttpMethodFilter.",
      "defaultValue": false
    },

搜索 spring.mvc.hiddenmethod.filter 进入 WebMvcAutoConfiguration 类,并定位到以下方法:

	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}

进入 OrderedHiddenHttpMethodFilter 方法中:

public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {
    public static final int DEFAULT_ORDER = -10000;
    private int order = -10000;

    public OrderedHiddenHttpMethodFilter() {
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }
}

进入起主要作用的类 HiddenHttpMethodFilter :

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
	HttpServletRequest requestToUse = request;
	if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
		String paramValue = request.getParameter(this.methodParam);
		if (StringUtils.hasLength(paramValue)) {
			String method = paramValue.toUpperCase(Locale.ENGLISH);
			if (ALLOWED_METHODS.contains(method)) {
				requestToUse = new HttpMethodRequestWrapper(request, method);
			}
		}
	}
	filterChain.doFilter((ServletRequest)requestToUse, response);
}

可以看到当请求方法是post时,获得请求的参数value(delete或put),如果参数不为空,把参数转换成大写,然后进行请求。

提供自定义入参的converter实现:

示例:

@GetMapping("teacher")
public String getTeacher(Teacher teacher){
	return teacher.toString();
}

请求时:

返回值:

想要得到正确的返回结果,需要在配置类里写,并标注为@Bean

@Bean
public WebMvcConfigurer webMvcConfigurer() {
	return new WebMvcConfigurer() {
		@Override
		public void addFormatters(FormatterRegistry registry) {
			registry.addConverter(new Converter<String, Teacher>() {
				  @Override
				  public Teacher convert(String s) {
					  String[] args = s.split(",");
					  Teacher teacher = new Teacher();
					  teacher.setAge(Integer.parseInt(args[1]));
					  teacher.setName(args[0]);
					  teacher.setSex(Integer.parseInt(args[2]));
					  return teacher;
				  }
			});
		}
	};
}

返回结果:

SpringApplication启动流程图:

由 SpringApplication.run(SpringboottestApplication.class, args); 进入run方法,

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return (new SpringApplication(primarySources)).run(args);
}

两条路: new SpringApplication(primarySources) 和 .run(args)

① new SpringApplication(primarySources):

primarySource为主启动类。

new SpringApplication时:

public SpringApplication(Class<?>... primarySources) {
	this((ResourceLoader)null, primarySources);
}

Ctrl点“this”:resourceLoader为空

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));
	// 确定走servlet技术栈
	this.webApplicationType = WebApplicationType.deduceFromClasspath(); 
	this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
	this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
	this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = this.deduceMainApplicationClass();
}

resourceLoader为空,deduceFromClasspath()方法:主要是确定使用哪套技术栈

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;
	}
}
}

以上代码的作用:在springboot的两套技术栈(reactive和servlet)选择其一:

 其中 SERVLET_INDICATOR_CLASSES 包含以下两个类:

private static final String[] SERVLET_INDICATOR_CLASSES 
= new String[]{"javax.servlet.Servlet", 
"org.springframework.web.context.ConfigurableWebApplicationContext"};

当这两个类都存在(一定都存在),返回SERVLET。

SpringApplication中最重要的方法:

getSpringFactoriesInstances():

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = this.getClassLoader();
	Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

(1)loadFactoryNames(type, classLoader):获得META-INF/spring.factories中的配置项名叫BootstrapRegistryInitializer的配置

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	ClassLoader classLoaderToUse = classLoader;
	if (classLoaderToUse == null) {
		classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
	}

	String factoryTypeName = factoryType.getName();
	return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

loadSpringFactories(classLoaderToUse) :

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
	Map<String, List<String>> result = (Map)cache.get(classLoader);
	if (result != null) {
		return result;
	} else {
		Map<String, List<String>> result = new HashMap();

		try {
			Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");

			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 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) {
			IOException ex = var14;
			throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", ex);
		}
	}
}

返回:接口名对应的实现名列表的map 

(2)createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names) :根据上面获得的接口names创建对象。

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
	List<T> instances = new ArrayList(names.size());
	Iterator var7 = names.iterator();

	while(var7.hasNext()) {
		String name = (String)var7.next();

		try {
			Class<?> instanceClass = ClassUtils.forName(name, classLoader);
			Assert.isAssignable(type, instanceClass);
			Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
			T instance = BeanUtils.instantiateClass(constructor, args);
			instances.add(instance);
		} catch (Throwable var12) {
			Throwable ex = var12;
			throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
		}
	}

	return instances;
}

 new 对象时的三个步骤:

① 找到对应的类 

② 调用构造函数

③ 创建对象

反射:找class 、 找constructure、new Instance()

this.deduceMainApplicationClass() : 找启动类的main方法

    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;
    }

综上:this.SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) 方法的作用是:加载入参的值,给启动类赋值,把springFactories相关的配置字符串转成实例

② run():

public ConfigurableApplicationContext run(String... args) {
	long startTime = System.nanoTime();
	DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
	ConfigurableApplicationContext context = null;
	this.configureHeadlessProperty();
	SpringApplicationRunListeners listeners = this.getRunListeners(args);
	listeners.starting(bootstrapContext, this.mainApplicationClass);

	Throwable ex;
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
		this.configureIgnoreBeanInfo(environment);
		Banner printedBanner = this.printBanner(environment);  // springboot启动的banner
		context = this.createApplicationContext();
		context.setApplicationStartup(this.applicationStartup);
		this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		this.refreshContext(context);  // 初始化IOC的整个生命周期
		this.afterRefresh(context, applicationArguments);
		Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
		if (this.logStartupInfo) {
			(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
		}

		listeners.started(context, timeTakenToStartup);
		this.callRunners(context, applicationArguments);
	} catch (Throwable var12) {
		ex = var12;
		this.handleRunFailure(context, ex, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
		listeners.ready(context, timeTakenToReady);
		return context;
	} catch (Throwable var11) {
		ex = var11;
		this.handleRunFailure(context, ex, (SpringApplicationRunListeners)null);
		throw new IllegalStateException(ex);
	}
}

refreshContext最终追踪到AbstractApplicationContext.class类的 refresh() 方法(一个属于spring的方法,springboot就是对spring加了个壳):初始化生命周期

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var10) {
                BeansException ex = var10;
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + ex);
                }

                this.destroyBeans();
                this.cancelRefresh(ex);
                throw ex;
            } finally {
                this.resetCommonCaches();
                contextRefresh.end();
            }

        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值