Springboot基础教程(3)--自动装配原理/静态资源处理/欢迎页

1、自动装配原理

Springboot的配置文件application.properties/application.yml到底能写什么?怎么写?

SpringBoot官方文档中有大量的配置,我们无法全部记住。

1.1、分析自动装配原理

spring.factories中找到HttpEncodingAutoConfiguration包。

HttpEncodingAutoConfigurationHttp编码自动配置)为例解释自动配置原理;

java 复制代码
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration(proxyBeanMethods = false)

//启动指定类的ConfigurationProperties功能;  
	//进入这个ServerProperties查看,将配置文件中对应的值和ServerProperties绑定起来;  
	//并把ServerProperties加入到ioc容器中
@EnableConfigurationProperties(ServerProperties.class)
//Spring底层@Conditional注解
	//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
	//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判断当前项目有没有这个类CharacterEncodingFilter;
	//SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
//判断配置文件中是否存在某个配置:server.servlet.encoding.enabled;
	//如果不存在,判断也是成立的  
	//即使我们配置文件中不配置server.servlet.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
	//只有一个有参构造器的情况下,参数的值才会从容器中拿
    public HttpEncodingAutoConfiguration(ServerProperties properties) {
       this.properties = properties.getServlet().getEncoding();
    }

    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean	//判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
       CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
       filter.setEncoding(this.properties.getCharset().name());
       filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
       filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
       return filter;
    }

    //...

}

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是有默认值或是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxProperties类中封装着;(例如这里的ServerProperties)
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类
java 复制代码
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
 	//...   
}

我们去配置文件里面试试前缀,看提示!


这就是自动装配的原理!

总而言之

1、SpringBoot启动会加载大量的自动配置类

2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了);

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

xxxxAutoConfigurartion:自动配置类; 给容器中添加组件

xxxxProperties:封装配置文件中相关属性;

1.2、@Conditional

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中是否存在指定Bean;
@ConditionalOnMissingBean 容器中是否不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

我们怎么知道哪些自动配置类生效?

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

yaml 复制代码
#开启springboot的调试类
debug: true

Positive matches:(自动配置类启用的:正匹配)


Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (无条件的类)

如何把Negative matches变为Positive matches呢?

Negative -> Positive的过程,就是让配置类生效的过程。配置类生效是SpringBoot实现自动装配的第一步。

  1. 最简单的方法:添加pom.xml依赖

让自动配置从 Negative 翻成 Positive 的最常见、也最容易踩的一关,确实就是把依赖加进来。

依赖进来 → 类路径就有了 → @ConditionalOnClass 这第一道门槛才算过

这道门槛一票否决 ,所以缺依赖时你会在 Negative 报告里看到did not find required class 'xxx.xxx.Xxx'

过了第一道门,后面还有若干小门槛

starter 只保证最低限度的类存在;是否最终 Positive 还要看后面几个常见条件:

  • @ConditionalOnMissingBean -- 你自己提前注册了一个同类型 Bean,自动配置就退位。
  • @ConditionalOnProperty -- 某些组件有显式开关,例如
    spring.jms.cached=false 会让 JmsAutoConfiguration 里的缓存工厂不生效。
  • @ConditionalOnWebApplication@ConditionalOnCloudPlatform 等 -- 非 Web 环境不会给 Web 组件 Positive。
    因此只加依赖但自定义了 Bean 或改了配置,仍可能回到 Negative
  1. 当然也存在最麻烦的方法,手动配置

    只要你手动把所需类塞进 classpath(自己写、复制导入jar包、gradle 都行),并且满足后续条件,自动配置一样生效;只是没人会闲着这么干,用官方 starter 最省心。

2、静态资源处理

2.1、静态资源映射规则

首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!

写请求非常简单,那我们要引入我们前端资源,我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?

如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,对吧!但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!

我们先来聊聊这个静态资源映射规则:

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;

我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;

有一个方法:addResourceHandlers 添加资源处理

java 复制代码
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    super.addResourceHandlers(registry);
    if (!this.resourceProperties.isAddMappings()) {
        // 已禁用默认资源处理
       logger.debug("Default resource handling disabled");
       return;
    }
    ServletContext servletContext = getServletContext();
    // webjars 配置
    addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
    // 静态资源配置
    addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
       registration.addResourceLocations(this.resourceProperties.getStaticLocations());
       if (servletContext != null) {
          registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
       }
    });
}

读一下源代码:比如所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;

什么是webjars?

Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。

使用SpringBoot需要使用Webjars,我们可以去搜索一下:

网站:https://www.webjars.org

要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!

xml 复制代码
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

导入完毕,查看webjars目录结构,并访问Jquery.js文件!

访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,我们这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

第二种静态资源映射规则

除了webjars以外,我们项目中要是使用自己的静态资源该怎么导入呢?我们看下一行代码;

我们去找getStaticPathPattern()发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类他返回的getStaticLocations(),我们可以点进去看一下分析:

java 复制代码
// 找到路径
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
       "classpath:/resources/", "classpath:/static/", "classpath:/public/" };

/**
 * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
 * /resources/, /static/, /public/].
 */
// 找到对应的值
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

// 进入方法
public String[] getStaticLocations() {
    return this.staticLocations;
}

ResourceProperties 可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。

所以得出结论,以下四个目录存放的静态资源可以被我们识别:

java 复制代码
"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"

我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;

比如我们访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件;

2.2、自定义静态资源路径

我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置;

properties 复制代码
spring.resources.static-locations=classpath:/coding/,classpath:/lingbo/

一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!

3、首页处理

静态资源文件夹说完后,我们继续向下看源码!可以看到一个欢迎页的映射,就是我们的首页!

java 复制代码
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
       FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
          new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),//获得欢迎页
          this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
    return welcomePageHandlerMapping;
}

点进去继续看

java 复制代码
private Resource getWelcomePage() {
    for (String location : this.resourceProperties.getStaticLocations()) {
       Resource indexHtml = getIndexHtml(location);
       if (indexHtml != null) {
          return indexHtml;
       }
    }
    ServletContext servletContext = getServletContext();
    if (servletContext != null) {
       return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION));
    }
    return null;
}

// 欢迎页就是一个location下的的 index.html 而已
private Resource getIndexHtml(String location) {
    return getIndexHtml(this.resourceLoader.getResource(location));
}

欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。

比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html

新建一个 index.html ,在我们上面的3个目录中任意一个;然后访问测试 http://localhost:8080/ 看结果!

相关推荐
likuolei1 小时前
XSL-FO 软件
java·开发语言·前端·数据库
凌波粒1 小时前
SpringBoot基础教程(2)--yaml/配置文件注入/数据校验/多环境配置
java·spring boot·后端·spring
S***26751 小时前
Spring Boot环境配置
java·spring boot·后端
6***83051 小时前
什么是Spring Boot 应用开发?
java·spring boot·后端
毕设源码柳学姐1 小时前
计算机毕设 java 智慧社区服务系统 SSM 框架社区生活平台 Java 开发的便民服务与互动系统
java·开发语言·生活
U***l8321 小时前
【postgresql】分区表管理
java·数据库·postgresql
倚肆1 小时前
MyBatis-Plus Mapper 接口方法详解
java·mybatis
n***78681 小时前
SpringBoot详解
java·spring boot·后端
s***P9821 小时前
Spring数据库原理 之 DataSource
java·数据库·spring