Spring boot
Spring Boot
是由Pivotal
团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置
。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
SpringBoot 框架中还有两个非常重要的策略:开箱即用
和约定大于配置
。
开箱即用
:是指在开发过程中,通过在MAVEN项目的pom文件中添加相关依赖包,然后使用对应注解来代替繁琐的XML配置文件以管理对象的生命周期。这个特点使得开发人员摆脱了复杂的配置工作以及依赖的管理工作,更加专注于业务逻辑。
约定大于配置
:是一种由SpringBoot本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化。
快速创建 Spring boot
1、官网创建
-
进入官网的后点击
Spring Initializr
也可以直接点 快速构建 Spring Boot -
进入页面填写相关信息
- 将下载的压缩包,解压并使用 IDEA 打开
创建完成
一般不使用这种方法创建
2、IDEA创建(常用)
-
打开
IDEA
新建项目。(填写和官网一样) -
删除无用的东西
-
运行程序
会结束进程
是因为我们没有添加 Web 依赖
-
pom.xml
中导入依赖
<!--版本会从父依赖里面找,不需要设置,也可以设置,设置什么版本优先使用自己设置的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 再次启动程序,(记得刷新 Maven依赖库)
- 访问 http://localhost:8080 (出现以下信息配置成功,只是没有对应页面)
3、修改启动端口号
在resources
目录下的 application.properties
中添加如下配置
# 更改项目启动端口
server.port=9999
修改后测试访问 http://localhost:9999 也能正常启动
在resources
目录下添加 banner.txt
里面填写的内容可以替换掉启动 LOG
Spring boot自动装配原理
1、依赖管理
项目依赖管理
创建 SpringBoot
项目,都会导入一个父依赖,如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
点开上面的依赖
里面还有一个父依赖,如下
<!--这个依赖包含了基本上会用到依赖版本-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.5</version>
</parent>
点开 spring-boot-dependencies
可以看到许多的依赖版本
<!--这个依赖很长,列出一小部分-->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.5</version>
<packaging>pom</packaging>
<name>spring-boot-dependencies</name>
<description>Spring Boot Dependencies</description>
<url>https://spring.io/projects/spring-boot</url>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
</license>
</licenses>
<developers>
<developer>
<name>Pivotal</name>
<email>info@pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
</developer>
</developers>
<scm>
<url>https://github.com/spring-projects/spring-boot</url>
</scm>
<properties>
<activemq.version>5.16.1</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.88</appengine-sdk.version>
<artemis.version>2.15.0</artemis.version>
<aspectj.version>1.9.6</aspectj.version>
<assertj.version>3.18.1</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.3</awaitility.version>
<bitronix.version>2.1.4</bitronix.version>
几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制,所有我们基本上不需要导入依赖
启动器
1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景,可以参考官网 Spring-Boot-starter 启动器
4、见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<!--所有场景启动器最底层的依赖,所有场景都需要这个启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.5</version>
<scope>compile</scope>
</dependency>
注意点:
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。
3、不想使用仲裁版本时,添加依赖,自己给出版本号就可以了
2、自动配置
-
自动配好Tomcat
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.4.5</version> <scope>compile</scope> </dependency>
-
自动配好SpringMVC
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.6</version> <scope>compile</scope> </dependency>
-
自动配好Web常见功能,如:字符编码问题,拦截器配置。
-
默认的包结构
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
无需以前的包扫描配置
想要改变扫描路径,@SpringBootApplication(scanBasePackages="com")
也可以将主程序上的
@SpringBootApplication
注解修改为下面三条记录@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan("com") 这三个注解和 @SpringBootApplication(scanBasePackages="com") 等价
-
各种配置拥有默认值
默认配置最终都是映射到某个类上,如:MultipartProperties
配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
-
按需加载所有自动配置项
所有自动配置,都是按需加载,当添加该项目的启动项时,才会配置。
SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
3、底层注解
在了解 Spring-boot
自动装配原理前,先了解一些底层注解。
@Configuration
告诉 SpringBoot
这是一个配置类相当于配置文件
/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
* Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
*/
@Configuration(proxyBeanMethods = false)
@Bean
:向IOC
容器注入一个类。
@Component
:将类注入 IOC 容器。
//@Component 等价于 <bean id="user" class="com.wyx.pojo.User"/>
@Component
public class User {
// @Value 等价于 <property name="name" value="王玉星"/>
@Value("但是公司")
public String name;
}
下面三个注解和 @Component
一样,只是使用场景不同
@Controller
:标志这是一个Controller组件(控制器)
@Service
:标志这是一个Service组件(Service层)
@Repository
:标志这是一个Dao组件 (Dao层)
@Bean 与 @Component 的区别
仔细观察,发现上面的两个注解都是,向IOC 容器注入一个类
但是一般他们的使用情况不太一样。一般@Confiuration配合@Bean使用,写在配置文件中,返回的是某一个对象的实例;而@Component是对应某一个类,配合@ComponentScan使用,然后让spring ioc容器实例化。而且使用@Confiugraion和@Bean, 会进行cglib增强,拿到的是一个代理对象;@Component拿到的是一个普通的java对象。
@ComponentScan
:扫描包
//扫描 com.wyx.dao 包 将下面的User类注入IOC容器
@componentScan("com.wyx.dao")
public class config(){
@Bean //方法名作为组件的id 返回类型就是组件类型,返回值就是容器中的实例
public User user(){
return new User();
}
}
@Import
: 合并配置类,类似于两个 beans.xml
合并
@Import(User.class) //参数类型是一个数组,可以用{}来添加多个
@Conditional:
条件装配,满足条件才开始装配。
@ImportResource
:导入资源
作用:以前的配置是使用配置文件 beans.xml
配置,不想在使用配置类来重新写配置文件,只需要在类上面写如下配置,就可以将配置文件的东西解析出来。
@ImportResource("classpath:beans.xml")
配置绑定
@Component
+ @ConfigurationProperties
1、在 application.properties
中添加配置属性
user.name=王玉星
user.sex=男
user.age=20
2、编写实体类
package com.wyx.dao;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "user")
public class User {
private String name;
private String sex;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
3、编写控制器
package com.wyx.controller;
import com.wyx.dao.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
@Autowired
User user;
@ResponseBody
@RequestMapping("/user")
public User user(){
return user;
}
}
4、测试方法
从结果可以看出,application.properties
中配置的属性被加入了类
第二种方法:将实体类中的 @Component
删除掉 ,在配置类上加入@EnableConfigurationProperties(User.class)
即可
4、自动装配原理
了解了Spring Boot的依赖,自动配置及其很多底层注解,下面我们开始深入了解自动装配原理。
自动配置原理图
1、自动包规则
从原理图中可以看出 @EnableAutoConfiguration
自动导入配置下面的注解有 @AutoConfigurationPackage
和 @Import(AutoConfigurationImportSelector.class)
分别是 自动配置包和自动选择导入配置。
自动配置包@AutoConfigurationPackage
,下面有@Import(AutoConfigurationPackages.Registrar.class)
,点击 Registrar
可以看到以下信息(部分)。
//继承ImportBeanDefinitionRegistrar和DeterminableImports
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
//重写它们的方法,利用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));
}
}
2、自动配置选择器
自动配置选择器,@Import(AutoConfigurationImportSelector.class)
点开类,可以看到 selectImports
方法
selectImports
方法接着调用了 getAutoConfigurationEntry
方法,查看 getAutoConfigurationEntry
方法
//此方法是获取候选的配置进行一系列处理后返回给getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//getCandidateConfigurations() 获取候选配置。
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//通过调试发现经过这一步后发现 configurations 由130减少到27
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
我们接着查看 getCandidateConfigurations
方法
// 获取spring-boot-autoconfigure-2.4.5.jar 下的META-INF/spring.factories 将它加载进来
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//利用Spring的工厂加载器加载 spring.factories 到类中
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
查看 spring-boot-autoconfigure-2.4.5.jar 下的 META-INF/spring.factories
如下
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
观察spring.factories
下面两张图片,发现开始是 21
结束时 151
刚好满足上面图片加载的 130
配置类加载的数量
然后我们接着调试
当走到以下位置时
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//getCandidateConfigurations() 获取候选配置。
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//通过调试发现经过这一步后发现 configurations 由130减少到23
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
由上面两张图,明显看出 configurations
大量减少,于是我们查看 getConfigurationClassFilter().filter();
getConfigurationClassFilter
调试发现,这个方法会将需要过滤的配置信息封装到 configurationClassFilter
变量中
private ConfigurationClassFilter getConfigurationClassFilter() {
// configurationClassFilter 在类初始化时就是 null 所以一定会走
if (this.configurationClassFilter == null) {
// 获取过滤规则这里过滤规则 会调用Spring工厂加载器的loadFactories方法,规则具体参见源码,不做过多讲解
/* 具体调用如下
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
*/ 下面的invokeAwareMethods查看源码说判断这个过滤规则需要用什么资源加载器加载,具体查看源码
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
for (AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter);
}
// 将过滤规则和beanClassLoader加载器通过ConfigurationClassFilter
this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}
上面的 configurationClassFilter
过滤规则就是将配置文件读取的(spring.factories)与 Spring 的 ioc
容器通过过滤规则比较,如果有则不添加到过滤范围,否则添加。具体对比参见ConfigurationClassFilter
类的构造器调用的 AutoConfigurationMetadataLoader.loadMetadata(classLoader)
,这个类(AutoConfigurationMetadataLoader)的主要功能就是将 spring-autoconfigure-metadata.properties(和spring.factories在同级目录)
的信息用过滤规则过滤后注入 autoConfigurationMetadata
常量
//这里是 ConfigurationClassFilter 的构造器,它下面封装了filter方法
ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
this.filters = filters;
}
解释一下,getAutoConfigurationImportFilters(过滤规则)
主要就是将容器中没有注入的容器的组件添加成规则,用于后面过滤。
容器是否添加 这里使用的是底层注解 @Conditional 来判断是否向ioc
容器注入组件
查看filter
方法是将配置文件spring.factories
中读取的信息与上一步经过处理的configurations
与autoConfigurationMetadata
进行比较,如果相同则不保留,取它们不相同的值,并返回结果。如果全相同也返回结果,如果logger.isTraceEnabled()
日志判断为 true 就打印日志信息
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations); //获取上一步经过处理的configurations
boolean skipped = false;
//将configurations与autoConfigurationMetadata(filter的类加载器加载出来的,具体看前面ConfigurationClassFilter的构造器)进行比较如果相同令它为空,相当于取不同的值(全部的configurations和过滤规则下autoConfigurationMetadata,相当于取SpringBoot已经需要加载的)
for (AutoConfigurationImportFilter filter : this.filters) {
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
if (logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return result;
}
}
使用filter
过滤后的配置文件就是需要加载的配置类。
到了这里我们了解了 Spring-Boot
是如何选择自动装配需要装配的组件了。
底层是通过反射(类加载器)判断容器中是否有该组件,在来修改装配的文件
。那么我们底层到底是如何装配的呢? 我们接着看下面
IOC 容器的组件自动装配
首先我们点开 spring-boot-autoconfigure-2.4.5.jar
查看
由上图可以看出很多的 XXXXAutoConfiguration
意思是XXX自动装配
随便点开一个类,这里点开的的 AopAutoConfiguration
通过上图,我们可以看出很多底层注解都是 @ConditionalXXX
意思是按照某种条件装配,这也是 SpringBoot底层注解的核心
,按照某种条件装配,如果条件成立才会装配配置类。我们随便举一些例子
//如果application.properties 配置文件中有spring.aop.auto等于true则装配,默认值就是true
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
//如果有Advice类则配置生效
@ConditionalOnClass(Advice.class)
//如果容器中没有这个Bean则装配
@ConditionalOnMissingBean
//容器中没有这个名字 multipartResolver 的组件则装配
@ConditionalOnMissingBean(name = multipartResolver)
//判断是上面类型的应用程序,是则配置
@ConditionalOnWebApplication(type = Type.SERVLET)
//如果容器中存在这个Bean才装配
@ConditionalOnBean(MultipartResolver.class)
/*
以下是SpringBoot的一个注解,作用就是保证名字规范
*/
@Bean
@ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
以上这些是常见的
更多的条件装配查看下图
总结:
-
SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
-
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。如果我们需要修改值可以通过
application.properties
直接修改,具体如何修改,看对应类的@ConditionalOnProperty
或@ConfigurationProperties()
或@EnableConfigurationProperties(User.class)
携带的值 -
生效的配置类就会给容器中装配很多组件
-
只要容器中有这些组件,对应组件的功能就能有
-
自定义配置
- 第一种方法修改
application.properties
对应组件的值 - 第二种方法利用注解原理使用
@Bean
@Component
等注解,来重写底层原理
- 第一种方法修改
以下是使用 SpringBoot
使用的最佳方法
引入场景依赖
查看自动配置了哪些(选做)
-
- 自己分析,引入场景对应的自动配置一般都生效了
- 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
是否需要修改
-
- 参照文档修改配置项
-
- 自定义加入或者替换组件
-
-
- @Bean、@Component
-
Yaml
YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。
非常适合用来做以数据为中心的配置文件
基本语法
- key: value;kv之间有空格
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格(
idea开发直接使用tab不会错
) - 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#'表示注释
- 字符串无需加引号,如果要加,单引号不转义,双引号转义
使用方法
- 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
- 对象:键值对的集合。map、hash、set、object
行内写法: k: {k1:v1,k2:v2,k3:v3}
#或
k:
k1: v1
k2: v2
k3: v3
- 数组:一组按次序排列的值。array、list、queue
行内写法: k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
配置绑定
自定义的类和配置文件绑定一般没有提示。
需要加入下面依赖,并且打包时不加入其中打包文件中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
SpringMVC自动配置
Spring Boot为Spring MVC提供了自动配置,适用于大多数应用程序
自动配置在Spring的默认值之上添加了以下功能:
- 包含
ContentNegotiatingViewResolver
(内容协商视图解析器) 和BeanNameViewResolver
(BeanName视图解析器)的 beans。 - 支持提供静态资源,包括对
WebJars
的支持。 - 自动注册
Converter
(转换器),GenericConverter
(通用转换器) 和Formatter
(格式化器) 的 beans。 - 支持
HttpMessageConverters
(HTTP消息转换器) 。 - 自动注册 MessageCodesResolver (国际化)。
- 静态 index.html 支持。
- 自定义 Favicon 支持。
- 自动使用
ConfigurableWebBindingInitializer
(可配置绑定初始化器)bean。
静态资源
-
默认情况下,Spring Boot从类路径中的
/static
(或/public
或/resources
或/META-INF/resources
)目录或ServletContext
的根目录中提供静态内容。-
默认情况下在上面列出的目录我们通过
项目发布路径/+资源名
都能访问到。 -
原理: 静态资源都是
/**
请求进来,先去找Controller
看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
-
-
它使用来自Spring MVC的
ResourceHttpRequestHandler
(Http资源请求处理程序) ,以便您可以通过添加自己的WebMvcConfigurer
并覆 盖addResourceHandlers
方法来修改该行为。
默认情况下,资源映射到 /**
,但您可以使用 spring.mvc.static-path-pattern
属性对其进行调整。例如,将所有资源重新定位到 /res/**
可以实现如下:
#在res文件夹下放入资源
spring:
mvc:
static-path-pattern: /res/**
上面是修改静态资源在哪个包下面,当然我们也可以用 spring.resources.static-locations
来改变Spring Boot默认的静态资源路径
# 修改默认的资源访问路径,参数是一个数组,可以传递多个值,请求的时候Servlet的根目录也会自动添加
spring:
web:
resources:
static-locations: [classpath:/res/,classpath:/static/]
Webjars
如果我们想要访问的资源是以Webjars
格式打成的jar
包,那么具有 /webjars/**
中路径 的任何资源都将从jar文件中提供。
https://www.webjars.org/ 这个路径是Webjars
打包的官网,可以去里面找相关jar
包的依赖
例如:我们引入在 pom.xml 中引入以下依赖
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
那么我们通过访问 项目发布路径/+/webjars/jquery/3.5.1/jquery.min.js
就能获得 jquery的相关信息(源码)
欢迎页
Spring Boot支持静态和模板化的欢迎页面。它首先在配置的静态内容位置中查找 index.html
文件。如果找不到,则会查找 index
模板。如果找到任何一个,它将自动用作应用程序的欢迎页面。
上面这段话的意思是:默认会找 index.html
如果没有找到,就会找 controller
处理 /index
请求的路径来作为欢迎页
注意点
-
可以配置静态资源路径
spring.resources.static-locations
-
但是不可以配置静态资源的访问前缀
spring.mvc.static-path-pattern
。否则导致index.html
和Favicon
不能被默认访问
Favicon
将 favicon.ico
放在静态资源目录下即可。
静态资源底层源码分析
首先我们找到 spring-boot-autoconfigure-2.4.5.jar
Spring自动配置的源码,接下来找 web
目录下的 WebMvcAutoConfiguration
配置类
这个类的注解生效规则如下
@Configuration(proxyBeanMethods = false) //声明是一个配置类
@ConditionalOnWebApplication(type = Type.SERVLET) //是Servlet类才生效
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) //存在这3个类才生效,一般导入mvc都会生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //没有这个类才生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) //控制配置类的加载顺序
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
// 从上面的配置看,基本上配置都会生效
接着看配置类分别配置了什么
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class,
org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
WebMvcProperties
它绑定了 spring.mvc
org.springframework.boot.autoconfigure.web.ResourceProperties
它绑定了 spring.resources
WebProperties
它绑定了 spring.web
查看配置类分别配置了什么
private final WebMvcProperties mvcProperties; //获取和spring.mvc绑定的所有的值的对象
private final ListableBeanFactory beanFactory; //获取 beanFactory IOC容器工厂 返回值是List
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider; //HTTP消息转换器
private final ObjectProvider<DispatcherServletPath> dispatcherServletPath; //视图解析器路径
private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations; //Servlet注册
final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; //资源处理程序注册
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
this.mvcProperties.checkConfiguration();
}
静态资源
配置静态资源,我们在类中找到一个方法addResourceHandlers
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
/*resourceProperties,一个变量在上面类中获取了spring.web.resources的值 isAddMappings()方法是获取一个变量为 addMappings 的值 默认值是true,由这个判断,我们可以将 spring.web.resources.addMappings的值设置为false,
就相当于禁用默认的静态资源规则。
*/
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//获取Servlet的Context 即项目发布路径
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));
}
});
}
从上面的源码分析,我们可以的到一个结论,我们要禁用静态资源规则只需要在配置文件中添加
# 添加这个可以禁用SpringBoot的默认静态资源规则
spring:
web:
resources:
add-mappings: false
欢迎页
配置欢迎页,我们在类中找一个方法 WelcomePageHandlerMapping
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
// 这里 new 了一个WelcomePageHandlerMapping
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
// WelcomePageHandlerMapping类 的构造器
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
//判断 welcomePage 默认值就是index.html,并且 /** 的请求路径等不等于静态资源路径。
if (welcomePage != null && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage);
// 如果是就设置视图解析器为转发到 index.html
setRootViewName("forward:index.html");
}
//如果上面条件不满足就 找controller请求有没有/index有则设置视图的解析为index
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
RestFul风格使用
RestFul
风格的讲解参考 SpringMVC
在 SpringBoot中使用 RestFul
风格 ,因为表单中只有 get
和post
两种提交方式,所以在SpringBoot
中使用 RestFul
风格必须走以下流程
1、开启 RestFul
风格支持
# 默认值是 false 需要开启才能使用
spring:
mvc:
hiddenmethod:
filter:
enabled: true
2、在提交的表单中添加以下隐藏域属性 <input type="text" name="_method" value="提交方法" hidden>
<!--原来的网页是这样写的-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user" method="get">
<input type="submit" value="get提交">
</form>
<form action="/user" method="post">
<input type="submit" value="post提交">
</form>
<form action="/user" method="put">
<input type="submit" value="put提交">
</form>
<form action="/user" method="delete">
<input type="submit" value="delete提交">
</form>
</body>
</html>
<!--使用RestFul风格后,提交的表单需要添加一个隐藏域,隐藏一个叫 _method-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user" method="get">
<input type="submit" value="get提交">
</form>
<form action="/user" method="post">
<input type="submit" value="post提交">
</form>
<form action="/user" method="post">
<input type="text" name="_method" value="put" hidden>
<input type="submit" value="put提交">
</form>
<form action="/user" method="post">
<input type="text" name="_method" value="delete" hidden>
<input type="submit" value="delete提交">
</form>
</body>
</html>
3、Controller
编写
package com.wyx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
public class RestFulController {
@ResponseBody
@GetMapping("/user")
public String get(){
return "get方式提交";
}
@ResponseBody
@PostMapping("/user")
public String post(){
return "post方式提交";
}
@ResponseBody
@PutMapping("/user")
public String put(){
return "put方式提交";
}
@ResponseBody
@DeleteMapping("/user")
public String delete(){
return "delete方式提交";
}
}
RestFul
风格底层源码分析
首先我们找到 spring-boot-autoconfigure-2.4.5.jar
Spring自动配置的源码,接下来找 web
目录下的 WebMvcAutoConfiguration
配置类,找到 hiddenHttpMethodFilter
方法,翻译为Http方法过滤,查看源码如下
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class) //如果没有这个Bean才注入
//spring.mvc.hiddenmethod.filter为true才注入,默认值是false,这里说明了第一步,我们为什么要修改配置文件为 true
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
接着查看它 new 的类 OrderedHiddenHttpMethodFilter
这个方法继承了HiddenHttpMethodFilter
类 `查看源码其中有一段
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//将原生的请求赋值给 requestToUse 后面在放行它 filterChain.doFilter((ServletRequest)requestToUse, response)
HttpServletRequest requestToUse = request;
//判断请求方式是不是 POST请求并查看请求有没有出错的地方,这里也说明了表单提交为什么要使用 post提交
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
/*获取请求参数中名为methodParam的参数,
methodParam在上面有定义:private String methodParam = "_method";,就是_method,这里也说明了为什么我们隐藏域
中要这样写 <input type="text" name="_method" value="提交方法" hidden>
*/
String paramValue = request.getParameter(this.methodParam);
//判断请求参数有没有长度
if (StringUtils.hasLength(paramValue)) {
//将请求参数转换为全大写,证明我们在写请求参数时不区分大小写
String method = paramValue.toUpperCase(Locale.ENGLISH);
/*ALLOWED_METHODS 静态常量
static { ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(
HttpMethod.PUT.name(),
HttpMethod.DELETE.name(),
HttpMethod.PATCH.name()));
}
支持 PUT DELETE PATCH 请求
//判断是不是支持的请求,如果是,就用HttpMethodRequestWrapper来包装它并返回给 requestToUse
*/
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
拓展
修改请求参数 _method
查看源码,我们知道,容器中没有HiddenHttpMethodFilter
我们才注入,我们不妨来自己注入 HiddenHttpMethodFilter
package com.wyx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
//声明是一个配置类
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean//注入IOC容器
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
// 查看源码,它的 methodParam参数可以修改值
methodFilter.setMethodParam("requestName");
return methodFilter;
}
}
// 编写完成后我们隐藏域中的
// <input type="text" name="_method" value="提交方法" hidden> 需要改写为
// <input type="text" name="requestName" value="提交方法" hidden>
SpringMVC配置扩展
上面是官方文档的一段翻译,意思是我们可以扩展 SpringMVC
的功能,实现功能的扩展,那么我们需要以下几步
1、创建一个类并实现 WebMvcConfigurer
接口
2、在配置定义的类上添加 @Configuration
注解
package com.wyx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
//添加Controller请求跳转视图,WebMvcConfigurer 里面有非常多的方法可以重写
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("index");
}
//自定义视图解析器
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
//将自定义的视图解析器装载到 IOC 容器
@Bean
public MyViewResolver myViewResolver(){
MyViewResolver resolver = new MyViewResolver();
return resolver;
}
}
官网特别说明:不能加 @EnableWebMvc
我们查看这个注解 它给我们导入了一个类 DelegatingWebMvcConfiguration
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
在查看 DelegatingWebMvcConfiguration
发现它继承 WebMvcConfigurationSupport
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}
当我们添加@EnableWebMvc
时就会将 WebMvcConfigurationSupport
注入IOC 容器,然而在 WebMvcAutoConfiguration
的配置类中有这么一条条件装配注解 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
如果存在这个类就不会生效。
总结:如果我们添加@EnableWebMvc
注解,那么Spring自动装配的功能都会失效。
Spring Boot拦截器
1、
编写拦截器类
SpringBoot
中有一个接口 HandlerInterceptor
专门用来做拦截功能,我们想要实现拦截器,可以编写一个类实现HandlerInterceptor
接口在重写接口中的三个方法即可。下面是三个方法的解释
preHandle():
目标方法执行之前执行
postHandle():
目标方法执行完成以后执行
afterCompletion():
页面渲染以后执行
package com.wyx.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if(loginUser == null){
request.setAttribute("msg","请登录后访问");
request.getRequestDispatcher("/").forward(request,response);
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
2、
将编写的拦截器类注入IOC
容器(实现WebMvcConfigurer的addInterceptors
)
package com.wyx.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//拦截过滤器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**") //拦截所有请求
.excludePathPatterns("/","/index.html","/css/*","/js/*","/img/*","/login"); //放行哪些请求
}
}
3、
当有多个拦截器时同时作用一个方法时
preHandle():
按照实现拦截器的顺序优先进行拦截,postHandle()
和afterCompletion()
按照拦截器的顺序,反向执行。(类似于IO流的先用后关操作
)
文件上传
SpringBoot 已经为我们向容器中添加了文件上传功能
MultipartFile,使用文件上传功能我们只需要
1、在表单中使用enctype="multipart/form-data"
代表是文件上传
2、在控制层中接收表单的参数解析成为@RequestPart("headerImg") MultipartFile headerImg
即可
使用方式
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
@PostMapping("/upload")
public String upload(@RequestPart("file") MultipartFile headerImg
) throws IOException {
if(!headerImg.isEmpty()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("H:\\cache\\"+originalFilename));
}
return "main";
}
Web原生组件注入(Servlet、Filter、Listener)
1、使用Servlet API
@ServletComponentScan(basePackages = "com.wyx.servlet") :指定原生Servlet组件都放在那里
@WebServlet(urlPatterns = "/my"):效果:直接响应,没有经过Spring的拦截器
@WebFilter(urlPatterns={"/css/*","/images/*"})
@WebListener
2、使用RegistrationBean
ServletRegistrationBean`, `FilterRegistrationBean`, and `ServletListenerRegistrationBean
@Configuration
public class MyRegistConfig {
@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet,"/my","/my02");
}
@Bean
public FilterRegistrationBean myFilter(){
MyFilter myFilter = new MyFilter();
// return new FilterRegistrationBean(myFilter,myServlet());
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
return new ServletListenerRegistrationBean(mySwervletContextListener);
}
}
连接数据源
1、整合JDBC
1、
导入依赖
<!--JDBC启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--MySQL连接驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2、
配置数据库连接源
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=true&useUnicode=true&charterEncoding=utf8&serverTimezone=GMT%2B8
password: 970699
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
3、
测试连接数据源
@SpringBootTest
class SpringbootDataJdbcApplicationTests {
//DI注入数据源
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
//看一下默认数据源
System.out.println(dataSource.getClass());
//获得连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//关闭连接
connection.close();
// 测试得到的数据源是 HikariDataSource
}
}
4、
使用JDBC模板
在 SpringBoot
中即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。
1、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。
2、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
3、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类
JdbcTemplate主要提供以下几类方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
- query方法及queryForXXX方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句。
package com.kuang.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/jdbc")
public class JdbcController {
/**
* Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
* JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作
* 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接
*/
@Autowired
JdbcTemplate jdbcTemplate;
//查询employee表中所有数据
//List 中的1个 Map 对应数据库的 1行数据
//Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
@GetMapping("/list")
public List<Map<String, Object>> userList(){
String sql = "select * from employee";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
//新增一个用户
@GetMapping("/add")
public String addUser(){
//插入语句,注意时间问题
String sql = "insert into employee(last_name, email,gender,department,birth)" +
" values ('狂神说','24736743@qq.com',1,101,'"+ new Date().toLocaleString() +"')";
jdbcTemplate.update(sql);
//查询
return "addOk";
}
//修改用户信息
@GetMapping("/update/{id}")
public String updateUser(@PathVariable("id") int id){
//插入语句
String sql = "update employee set last_name=?,email=? where id="+id;
//数据
Object[] objects = new Object[2];
objects[0] = "秦疆";
objects[1] = "24736743@sina.com";
jdbcTemplate.update(sql,objects);
//查询
return "updateOk";
}
//删除用户
@GetMapping("/delete/{id}")
public String delUser(@PathVariable("id") int id){
//插入语句
String sql = "delete from employee where id=?";
jdbcTemplate.update(sql,id);
//查询
return "deleteOk";
}
}
2、整合Druid
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。
1、
导入Druid依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
2、
切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 Hikari
数据源,但可以 通过 spring.datasource.type
指定数据源。
spring:
datasource:
username: root
password: 970699
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源
下面是对 Druid
数据源的一些参数配置讲解
spring:
datasource:
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
我们以上配置的属性该如何让它生效呢?
利用 SpringBoot
的自动配置原理,我们只需要编写一个类来获取数据即可
@Configuration
public class DruidConfig {
/*
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}
}
测试自己配置的东西是否生效
@SpringBootTest
class SpringbootDataJdbcApplicationTests {
//DI注入数据源
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
//看一下默认数据源
System.out.println(dataSource.getClass());
//获得连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
DruidDataSource druidDataSource = (DruidDataSource) dataSource;
System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());
//关闭连接
connection.close();
}
}
Druid的后台监控功能实现
//由于没有Web.xml配置文件,所以使用 Spring Boot 的注册 Servlet 方式
@Configuration
public class MyRegistConfig {
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
// 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
initParams.put("loginPassword", "123456"); //后台管理界面的登录密码
//后台允许谁可以访问
//initParams.put("allow", "localhost"):表示只有本机可以访问
//initParams.put("allow", ""):为空或者为null时,表示允许所有访问
initParams.put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问
//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
//配置过滤器,因为SpringBoot中配置的过滤器并不会拦截原生组件配置的Servlet请求
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);
//"/*" 表示过滤所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
访问:http://localhost:8080/druid/login.html 可以查看监控页面
3、整合Mybatis
(重点)
1、
导入相关依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
2、
配置数据源
spring:
datasource:
username: root
password: 970699
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
3、
创建实体类
package com.wyx.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private Integer age;
private String sex;
}
4、
创建Mapper接口
//@Mapper : 表示本类是一个 MyBatis 的 Mapper
@Mapper
@Repository
public interface DepartmentMapper {
// 获取所有用户
List<User> getUsers();
// 通过姓名获得用户
User getUser(String name);
}
5、
编写对应的Mapper文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wyx.mapper.User">
<select id="getUsers" resultType="User">
select * from user;
</select>
<select id="getUser" resultType="User" parameterType="string">
select * from user where name = #{name};
</select>
</mapper>
6、
处理Maven资源过滤问题
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
7、
编写测试方法
@RestController
public class UserController {
@Autowired
UserMapper userMapper;
@GetMapping("/getUser")
public List<Department> getUsers(){
return userMapper.userMapper();
}
@GetMapping("/getDepartment/{name}")
public Department getDepartment(@PathVariable("name") String name){
return userMapper.getUser(name);
}
}