尚硅谷雷丰阳的SpringBoot零基础入门教程
本视频笔记地址: https://yuque.com/atguigu/springboot
本视频源码地址: https://gitee.com/leifengyang/springboot2
Spring官网: https://spring.io/
SpringBoot2的环境要求
一.SpringBoot2核心技术-基础入门 1.Spring能做什么 这里的Spring指的是整个Spring生态
微服务 响应式编程 分布式 WEB开发 无服务开发 事件驱动 批处理
2.SpringBoot Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
能快速创建出生产级别的Spring应用
SpringBoot是整合Spring技术栈的一站式框架
SpringBoot是简化Spring技术栈的快速开发脚手架
2.1 SpringBoot的优点 ● Create stand-alone Spring applications ○ 创建独立Spring应用 ● Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files) ○ 内嵌web服务器 ● Provide opinionated ‘starter’ dependencies to simplify your build configuration ○ 自动starter依赖,简化构建配置 ● Automatically configure Spring and 3rd party libraries whenever possible ○ 自动配置Spring以及第三方功能 ● Provide production-ready features such as metrics, health checks, and externalized configuration ○ 提供生产级别的监控、健康检查及外部化配置 ● Absolutely no code generation and no requirement for XML configuration ○ 无代码生成、无需编写XML
2.2、SpringBoot缺点
人称版本帝,迭代快,需要时刻关注变化(说明社区活跃,也是个优点)
封装太深,内部原理复杂,不容易精通
2.3 官方文档 进入官网 找到SpringBoot 点击Reference Doc进入官方文档
3.SpringBoot入门 1.系统的要求
Java 8 & 兼容java14 .
Maven 3.3+
idea 2019.1.2
**Maven的配置教程: **
Maven配置文件settings.xml | The Blog (qingling.icu)
开发环境的搭建 | The Blog (qingling.icu)
2.HelloWorld 需求:浏览发送/hello请求,响应 Hello,Spring Boot 2
1创建maven工程
2.引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.3.4.RELEASE</version > </parent > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > </dependencies >
3.创建主程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.atguigu.boot;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class MainApplication { public static void main (String[] args) { SpringApplication.run(MainApplication.class,args); } }
4.编写Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.atguigu.boot.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class HelloController { @RequestMapping public String handle01 () { return "Hello,Spring Boot2" ; } }
3.简化配置 官网地址:https://docs.spring.io/spring-boot/docs/2.7.14/reference/html/application-properties.html#appendix.application-properties
4.简化部署 通过在pom.xml文件中插入这个插件,可以直接将项目打成jar包简化部署
1 2 3 4 5 6 7 8 <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build >
可以直接使用cmd切换到target目录下使用java -jar
命令运行jar包
注意点
去掉cmd的快速编辑模式(不去掉启动的时候点击屏幕会停止)
4.了解自动装配原理 4.1 依赖管理 1.SpringBoot的自动版本仲裁机制 1 2 3 4 5 6 7 8 9 10 11 12 13 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.3.4.RELEASE</version > </parent > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > 2.3.4.RELEASE</version > </parent >
自定义mysql版本驱动
在父依赖中mysql是8版本的
可以在pom.xml中通过自定义properties修改版本
1 2 3 4 <properties > <mysql.version > 5.1.43</mysql.version > </properties >
2.Starters场景启动器 官网: https://docs.spring.io/spring-boot/docs/2.7.14/reference/html/using.html#using.build-systems.starters
以spring-boot-starter-*
起头的starter为官方提供的starter,*-spring-boot-starter
为第三方提供的starter
常见的Starter
以引入spring-boot-starter-web
为例,引入该starter它会帮我们引入以下的其它依赖
4.2 自动配置
自动配置Tomcat
引入依赖
配置Tomcat
自动配置SpringMvc
引入springMvc全套组件
自动配置好springMvc的常用组件(字符编码,文件上传解析器,dispatcherServlet等)
1 2 3 4 5 6 7 8 9 10 11 12 13 @SpringBootApplication public class MainApplication { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); String[] beanDefinitionNames = run.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } }
默认的包结构
主程序所在的包以及其的子包都可以被扫描到,无需配置包扫描
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 com +- example +- myapplication +- MyApplication.java #主程序 | +- customer | +- Customer.java | +- CustomerController.java | +- CustomerService.java | +- CustomerRepository.java | +- order +- Order.java +- OrderController.java +- OrderService.java +- OrderRepository.java
如果需要扫描的文件在主程序的上级目录,我们也想扫描到它,我们需要扩大一下包的扫描范围
1 2 3 @SpringBootApplication(scanBasePackages = "com.atguigu") 或者 @ComponentScan("com.atguigu")
各种配置拥有默认值
按需加载所有的自动配置项
spingboot的所有配置存在于下面的这个依赖中
1 2 3 4 5 6 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-autoconfigure</artifactId > <version > 2.3.4.RELEASE</version > <scope > compile</scope > </dependency >
4.3 容器功能 1.组件添加 1.1 @Configuration
基本使用
Full模式与Lite模式
配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Configuration(proxyBeanMethods = false) public class MyConfig { @Bean public User user01 () { User zhangsan = new User ("zhangsan" , 18 ); zhangsan.setPet(tomcatPet()); return zhangsan; } @Bean("tom") public Pet tomcatPet () { return new Pet ("tomcat" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan("com.atguigu.boot") public class MainApplication { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); String[] names = run.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } Pet tom01 = run.getBean("tom" , Pet.class); Pet tom02 = run.getBean("tom" , Pet.class); System.out.println("组件:" +(tom01 == tom02)); MyConfig bean = run.getBean(MyConfig.class); System.out.println(bean); User user = bean.user01(); User user1 = bean.user01(); System.out.println(user == user1); User user01 = run.getBean("user01" , User.class); Pet tom = run.getBean("tom" , Pet.class); System.out.println("用户的宠物:" +(user01.getPet() == tom)); } }
1.2 @Bean @Component @Controller @Service @Repository 1 2 3 4 5 @Bean public User user01 () { return new User ("Tom" ,100 ); }
1.3 @ComponentScan @Import 1 2 3 4 5 6 7 @Import({User.class, DBHelper.class}) @Configuration(proxyBeanMethods = false) public class MyConfig {}
1.4 @Conditional 条件装配:满足Conditional指定的条件,则进行组件注入
1 2 3 @ConditionalOnBean(name = "tom") @ConditionalOnMissingBean(name = "tom")
2.原生配置文件的引入 2.1、@ImportResource beans.xml*)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd" > <bean id ="haha" class ="com.atguigu.boot.bean.User" > <property name ="name" value ="zhangsan" > </property > <property name ="age" value ="18" > </property > </bean > <bean id ="hehe" class ="com.atguigu.boot.bean.Pet" > <property name ="name" value ="tomcat" > </property > </bean > </beans >
在java类中红使用@ImportResource(“classpath:beans.xml”)导入上面的配置文件
1 2 @ImportResource("classpath:beans.xml") public class MyConfig {}
3.配置绑定 3.1 @ConfigurationProperties 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Component @ConfigurationProperties(prefix = "mycar") public class Car { private String brand; private Integer price; public String getBrand () { return brand; } public void setBrand (String brand) { this .brand = brand; } public Integer getPrice () { return price; } public void setPrice (Integer price) { this .price = price; } @Override public String toString () { return "Car{" + "brand='" + brand + '\'' + ", price=" + price + '}' ; } }
配置文件中的值
1 2 mycar.brand =BYD mycar.price =120000
编写测试的controller
1 2 3 4 5 6 @Autowired private Car car;@RequestMapping("/car") public Car ggetCar () { return car; }
4.4 自动配置原理入门 1.引导加载自动配置类 @SpringBootApplication
1 2 3 4 5 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication{}
@EnableAutoConfiguration
1 2 3 @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}
@AutoConfigurationPackage
1 2 @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {}
Registrar.class
利用Registrar给容器中导入com.atguigu.boot(主程序所在的包)包下的一系列组件
@Import(AutoConfigurationImportSelector.class)
1 2 3 4 5 6 1 、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件2 、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类3 、利用工厂加载 Map<String, List<String>> loadSpringFactories (@Nullable ClassLoader classLoader) ;得到所有的组件4 、从META-INF/spring.factories位置来加载一个文件。 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件 spring-boot-autoconfigure-2.3 .4 .RELEASE.jar包里面也有META-INF/spring.factories
AutoConfigurationImportSelector.java
1 2 3 4 5 6 7 8 @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
getAutoConfigurationEntry()方法
利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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); }
**getCandidateConfigurations()**获取所有候选的配置
调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
loadSpringFactories()
利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private static Map<String, List<String>> loadSpringFactories (@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null ) { return result; } else { try { Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories" ) : ClassLoader.getSystemResources("META-INF/spring.factories" ); MultiValueMap<String, String> result = new LinkedMultiValueMap (); 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[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for (int var11 = 0 ; var11 < var10; ++var11) { String factoryImplementationName = var9[var11]; result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException ("Unable to load factories from location [META-INF/spring.factories]" , var13); } } }
从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件,spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
在spring.factories这个配置文件中写死了springBoot一启动就要给容器加载的所有配置类,一共127个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 # Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener # Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition # 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.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.r2dbc.R2dbcTransactionManagerAutoConfiguration,\ 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.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\ org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\ 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 # Failure analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,\ org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\ org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\ org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\ org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\ org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\ org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer # Template availability providers org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
2.按需开启自动配置项 虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration,按照条件装配规则(@Conditional),最终会按需配置,比如下面的配置是否生效还要通过@Conditional的条件判断。
简单的一句话:启动的时候加载所有,使用的时候按照条件(@Conditional)进行装配
3.修改默认配置 相当于当我们在容器中注入了类型为MultipartResolver但是id不为multipartResolver的组件的时候,会帮我们规范一下命名,命名为multipartResolver。
1 2 3 4 5 6 7 @Bean @ConditionalOnBean(MultipartResolver.class) @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) public MultipartResolver multipartResolver (MultipartResolver resolver) { return resolver; }
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
1 2 3 4 5 6 7 8 9 @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; }
总结:
xxxxxAutoConfiguration —> 组件 —> xxxxProperties取值 —-> application.properties
3.4.最佳实践
引入场景依赖
查看自动配置了哪些(选做)
自己分析,引入场景对应的自动配置一般都生效了
配置文件中debug=true开启自动配置报告 。Negative(不生效)\Positive(生效)
是否需要修改
4.5 简化开发 1.Lombok 引入依赖(使用前需要安装插件)
1 2 3 4 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </dependency >
注解
1 2 3 4 5 @NoArgsConstructor @AllArgsConstructor @Data @ToString @EqualsAndHashCode
2.简化日志开发
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <optional > true</optional > </dependency >
项目或者页面修改以后:Ctrl+F9;
4.Spring Initailizr(项目初始化向导)
二.SpringBoot2核心技术-核心功能
一.配置文件 1、文件类型 1.1、properties 同以前的properties用法
1.2、yaml 1.2.1、简介 YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言)。
非常适合用来做以数据为中心的配置文件
1.2.2、基本语法
key: value;kv之间有空格
大小写敏感
使用缩进表示层级关系
缩进不允许使用tab,只允许空格
缩进的空格数不重要,只要相同层级的元素左对齐即可
‘#’表示注释
字符串无需加引号,如果要加,’’与””表示字符串内容 会被 转义/不转义
1.2.3、数据类型
字面量:单个的、不可再分的值。date、boolean、string、number、null
对象:键值对的集合。map、hash、set、object
1 2 3 4 5 6 行内写法: k: {k1:v1 ,k2:v2 ,k3:v3 }k: k1: v1 k2: v2 k3: v3
数组:一组按次序排列的值。array、list、queue
1 2 3 4 5 6 行内写法: k: [v1 ,v2 ,v3 ]k: - v1 - v2 - v3
1.2.4、示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @ConfigurationProperties(prefix="person") @Data public class Person { private String userName; private Boolean boss; private Date birth; private Integer age; private Pet pet; private String[] interests; private List<String> animal; private Map<String, Object> score; private Set<Double> salarys; private Map<String, List<Pet>> allPets; } @Data public class Pet { private String name; private Double weight; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 person: userName: zhangsan boss: false birth: 2019 /12/12 20 :12:33 age: 18 pet: name: tomcat weight: 23.4 interests: [篮球 ,游泳 ] animal: - jerry - mario score: english: first: 30 second: 40 third: 50 math: [131 ,140 ,148 ] chinese: {first: 128 ,second: 136 } salarys: [3999 ,4999.98 ,5999.99 ] allPets: sick: - {name: tom } - {name: jerry ,weight: 47 } health: [{name: mario ,weight: 47 }]
2、配置提示 自定义的类和配置文件绑定一般没有提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <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 >
二.Web开发
1.简单的功能分析 1.1 静态资源访问 1.静态资源目录 只要静态资源放在类路径下: /static
(or /public
or /resources
or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
原理: 静态映射/**
请求进来,先去找Controller看能不能处理,不能处理的所有请求又都交给静态资源处理器 ,静态资源也找不到则响应404页面
改变默认的静态资源路径
1 2 3 spring: resources: static-locations: [classpath:/haha/ ]
2.静态资源访问前缀 默认无前缀
默认的访问路径: localhost:8080/xxx.png
设置访问前缀之后: localhost:8080/res/xxx.png
(文件在目录中的位置没有改变,只是访问的时候加上了res这一层路径)
1 2 3 spring: mvc: static-path-pattern: /res/**
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
3.webjar 自动映射 /webjars /**
https://www.webjars.org/
1 2 3 4 5 <dependency > <groupId > org.webjars</groupId > <artifactId > jquery</artifactId > <version > 3.5.1</version > </dependency >
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径
1.2 欢迎页支持
静态资源路径下 index.html
可以配置静态资源路径
但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
1 2 3 4 5 6 spring: resources: static-locations: [classpath:/haha/ ]
1.3 自定义 Favicon
favicon.ico 放在静态资源目录下即可(名称为favicon.ico)
1.4 静态资源配置原理 1.SpringMvc的自动配置类WebMvcAutoConfiguration
1 2 3 4 5 6 7 8 @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration {}
2.SpringMvc给容器中配置的组件
1 2 3 4 5 @Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
3.绑定的配置
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
WebMvcProperties.class
ResourceProperties.class
2.请求参数处理与数据响应 2.1、请求映射 1、rest使用与原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @RequestMapping(value = "/user",method = RequestMethod.GET) public String getUser () { return "GET-张三" ; } @RequestMapping(value = "/user",method = RequestMethod.POST) public String saveUser () { return "POST-张三" ; } @RequestMapping(value = "/user",method = RequestMethod.PUT) public String putUser () { return "PUT-张三" ; } @RequestMapping(value = "/user",method = RequestMethod.DELETE) public String deleteUser () { return "DELETE-张三" ; } @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter () { HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter (); methodFilter.setMethodParam("_m" ); return methodFilter; }
Rest原理(表单提交要使用REST的时候)
Tips :
Rest使用客户端工具:如PostMan直接发送put、delete等方式请求,无需Filter,不需要转换
扩展点:
如何将<input name="_method" type="hidden" value="delete"/>
中的name值_method改为自定义的值?
1 2 3 4 5 6 7 8 9 @Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter () { HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter (); methodFilter.setMethodParam("_m" ); return methodFilter; } }
2、请求映射原理 DispatcherServlet 继承 FrameworkServlet
FrameworkServlet 继承 HttpServletBean
FrameworkServlet 重写了doGet和doPost方法
DispatcherServlet的doDispatch方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null ; boolean multipartRequestParsed = false ; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null ; Exception dispatchException = null ; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); mappedHandler = getHandler(processedRequest); if (mappedHandler == null ) { noHandlerFound(processedRequest, response); return ; } HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET" .equals(method); if (isGet || "HEAD" .equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest (request, response).checkNotModified(lastModified) && isGet) { return ; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return ; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return ; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException ("Handler dispatch failed" , err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException ("Handler processing failed" , err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null ) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
getHandler()方法 遍历查找可以处理请求的方法
1 2 3 4 5 6 7 8 9 10 11 12 @Nullable protected HandlerExecutionChain getHandler (HttpServletRequest request) throws Exception { if (this .handlerMappings != null ) { for (HandlerMapping mapping : this .handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null ) { return handler; } } } return null ; }
**HandlerMapping ** 处理器映射
默认有5个HandlerMapping
RequestMappingHandlerMapping :保存了所有@RequestMapping 和handler的映射规则
所有的请求映射都在HandlerMapping中
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 访问 /能访问到index.html;
SpringBoot自动配置了默认的 RequestMappingHandlerMapping
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息
如果有就找到这个请求对应的handler
如果没有就是下一个 HandlerMapping
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping 自定义 HandlerMapping
2.2、普通参数与基本注解 1.注解 1 2 3 4 5 6 7 @PathVariable @RequestHeader @RequestAttribute @RequestParam @MatrixVariable @CookieValue @RequestBody
@PathVariable
接受路径中的参数也可以使用map集合接受,但是map集合必须是Map<String, String>的形式
@RequestHeader
获取指定的请求头或者所有的请求头信息
@RequestParam
可以使用map接受所有的请求参数
@CookieValue
获取cookie的值,可以直接获取字符串的值也可以封装成cookie的对象
@RequestBody
这里用一个字符串接收表单数据和平时使用的不一样,开发中使用一个对象接受表单的数据
1 2 3 4 5 6 7 8 9 @PostMapping("/save") public Map postMethod (@RequestBody String content) { Map<String,Object> map = new HashMap <>(); map.put("content" ,content); return map; }
@RequestAttribute
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.atguigu.boot.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestAttribute;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;import java.util.HashMap;import java.util.Map;@Controller public class RequestController { @GetMapping("/goto") public String goToPage (HttpServletRequest request) { request.setAttribute("msg" ,"获取请求域中的数据成功" ); request.setAttribute("code" ,200 ); return "forward:/success" ; } @ResponseBody @GetMapping("/success") public Map success (@RequestAttribute(value = "msg",required = false) String msg, @RequestAttribute(value = "code",required = false) Integer code,//通过注解的方式获取上面请求域的值 HttpServletRequest request) { Map<String,Object> map = new HashMap <>(); map.put("msg" ,msg); map.put("code" ,code); return map; } }
@MatrixVariable
开启SpringBoot的矩阵变量功能
1 2 3 4 5 6 7 8 9 10 11 12 @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void configurePathMatch (PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper (); urlPathHelper.setRemoveSemicolonContent(false ); configurer.setUrlPathHelper(urlPathHelper); } }; }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @GetMapping("/cars/{path}") public Map carsSell (@MatrixVariable("low") Integer low, @MatrixVariable("brand") List<String> brand, @PathVariable("path") String path) { Map<String,Object> map = new HashMap <>(); map.put("low" ,low); map.put("brand" ,brand); map.put("path" ,path); return map; } @GetMapping("/boss/{bossId}/{empId}") public Map boss (@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge, @MatrixVariable(value = "age",pathVar = "empId") Integer empAge) { Map<String,Object> map = new HashMap <>(); map.put("bossAge" ,bossAge); map.put("empAge" ,empAge); return map; }
2.Servlet API WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 以上的部分参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public boolean supportsParameter (MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || Principal.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || ZoneId.class == paramType); }
3.复杂参数: Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
向请求域中共享数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @GetMapping("/params") public String testParam (Map<String,Object> map, Model model, HttpServletRequest request, HttpServletResponse response) { map.put("hello" ,"world666" ); model.addAttribute("world" ,"hello666" ); request.setAttribute("message" ,"HelloWorld" ); Cookie cookie = new Cookie ("c1" ,"v1" ); response.addCookie(cookie); return "forward:/success" ; }
4.自定义对象参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Data public class Person { private String userName; private Integer age; private Date birth; private Pet pet; } @Data public class Pet { private String name; private String age; }
页面测试代码
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" > <title>Title</title> </head> <body> <h1>atguigu,欢迎您</h1> 测试REST风格; <form action="/user" method="get" > <input value="REST-GET 提交" type="submit" /> </form> <form action="/user" method="post" > <input value="REST-POST 提交" type="submit" /> </form> <form action="/user" method="post" > <input name="_method" type="hidden" value="delete" /> <input name="_m" type="hidden" value="delete" /> <input value="REST-DELETE 提交" type="submit" /> </form> <form action="/user" method="post" > <input name="_method" type="hidden" value="PUT" /> <input value="REST-PUT 提交" type="submit" /> </form> <hr/> 测试基本注解: <ul> <a href="car/3/owner/lisi?age=18&inters=basketball&inters=game" >car/{id}/owner/{username}</a> <li>@PathVariable (路径变量)</li> <li>@RequestHeader (获取请求头)</li> <li>@RequestParam (获取请求参数)</li> <li>@CookieValue (获取cookie值)</li> <li>@RequestBody (获取请求体[POST])</li> <li>@RequestAttribute (获取request域属性)</li> <li>@MatrixVariable (矩阵变量)</li> </ul> /cars/{path}?xxx=xxx&aaa=ccc queryString 查询字符串。@RequestParam ;<br/> /cars/sell;low=34 ;brand=byd,audi,yd ;矩阵变量 <br/> 页面开发,cookie禁用了,session里面的内容怎么使用; session.set(a,b)---> jsessionid ---> cookie ----> 每次发请求携带。 url重写:/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递. /boss/1 /2 /boss/1 ;age=20 /2 ;age=20 <a href="/cars/sell;low=34;brand=byd,audi,yd" >@MatrixVariable (矩阵变量)</a> <a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd" >@MatrixVariable (矩阵变量)</a> <a href="/boss/1;age=20/2;age=10" >@MatrixVariable (矩阵变量)/boss/{bossId}/{empId}</a> <br/> <form action="/save" method="post" > 测试@RequestBody 获取数据 <br/> 用户名:<input name="userName" /> <br> 邮箱:<input name="email" /> <input type="submit" value="提交" /> </form> <ol> <li>矩阵变量需要在SpringBoot中手动开启</li> <li>根据RFC3986的规范,矩阵变量应当绑定在路径变量中!</li> <li>若是有多个矩阵变量,应当使用英文符号;进行分隔。</li> <li>若是一个矩阵变量有多个值,应当使用英文符号,进行分隔,或之命名多个重复的key即可。</li> <li>如:/cars/sell;low=34 ;brand=byd,audi,yd</li> </ol> <hr/> 测试原生API: <a href="/testapi" >测试原生API</a> <hr/> 测试复杂类型:<hr/> 测试封装POJO; <form action="/saveuser" method="post" > 姓名: <input name="userName" value="zhangsan" /> <br/> 年龄: <input name="age" value="18" /> <br/> 生日: <input name="birth" value="2019/12/10" /> <br/> <!-- 宠物姓名:<input name="pet.name" value="阿猫" /><br/>--> <!-- 宠物年龄:<input name="pet.age" value="5" />--> 宠物: <input name="pet" value="小猫" /> <input type="submit" value="保存" /> </form> <br> </body> </html>
2.3 参数处理原理 1.页面发送请求
1 http://localhost:8080/car/3/owner/lisi
2.进入DispatcherServlet的doDispatch( )方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null ; boolean multipartRequestParsed = false ; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null ; Exception dispatchException = null ; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); mappedHandler = getHandler(processedRequest); if (mappedHandler == null ) { noHandlerFound(processedRequest, response); return ; } HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET" .equals(method); if (isGet || "HEAD" .equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest (request, response).checkNotModified(lastModified) && isGet) { return ; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return ; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return ; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException ("Handler dispatch failed" , err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException ("Handler processing failed" , err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null ) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
3.封装一个方法获取对应的处理器适配器
1 2 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
4.遍历所有的处理器适配器(一共有四种,如下图)找到支持的适配器
1 2 3 4 5 6 7 8 9 10 11 protected HandlerAdapter getHandlerAdapter (Object handler) throws ServletException { if (this .handlerAdapters != null ) { for (HandlerAdapter adapter : this .handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException ("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler" ); }
上图中四个处理器适配器中前两个是常用的处理器适配器: 0 - 支持方法上标注@RequestMapping 1 - 支持函数式编程的
5.查看当前的处理器适配器是否支持的方法
1 2 3 4 5 6 @Override public final boolean supports (Object handler) { return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); }
遍历查找之后返回的适配器是RequestMappingHandlerAdapter
6.回到DispatcherServlet执行下面的方法
1 2 3 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
7.内部的处理过程
handle( )方法
1 2 3 4 5 6 @Override @Nullable public final ModelAndView handle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); }
进入当前处理器适配器的handleInternal( )方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Override protected ModelAndView handleInternal (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); if (this .synchronizeOnSession) { HttpSession session = request.getSession(false ); if (session != null ) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this .cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }
执行invokeHandlerMethod( )方法
1 mav = invokeHandlerMethod(request, response, handlerMethod);
进入invokeHandlerMethod()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 @Nullable protected ModelAndView invokeHandlerMethod (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest (request, response); try { WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this .argumentResolvers != null ) { invocableMethod.setHandlerMethodArgumentResolvers(this .argumentResolvers); } if (this .returnValueHandlers != null ) { invocableMethod.setHandlerMethodReturnValueHandlers(this .returnValueHandlers); } invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this .parameterNameDiscoverer); ModelAndViewContainer mavContainer = new ModelAndViewContainer (); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this .ignoreDefaultModelOnRedirect); AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this .asyncRequestTimeout); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this .taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this .callableInterceptors); asyncManager.registerDeferredResultInterceptors(this .deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0 ]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]" ; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null ; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
注意这里的26个参数解析器,参数解析器的作用是确定将要执行的目标方法的每一个参数的值是什么
这里的每一个参数解析器都对应了我们标注在参数上的注解
SpringMVC目标方法能写多少种参数类型取决于参数解析器
参数解析器的接口设计
当前解析器是否支持解析这种参数
支持就调用 resolveArgument
返回值处理器
定义了controller返回值的类型
RequestMappingHandlerAdapter的invokeAndHandle( )方法真正的执行请求
1 invocableMethod.invokeAndHandle(webRequest, mavContainer);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public void invokeAndHandle (ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null ) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true ); return ; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true ); return ; } mavContainer.setRequestHandled(false ); Assert.state(this .returnValueHandlers != null , "No return value handlers" ); try { this .returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } }
执行的过程
1 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
1 2 3 4 5 6 7 8 9 10 11 @Nullable public Object invokeForRequest (NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } return doInvoke(args); }
获取方法参数值的过程
1 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } Object[] args = new Object [parameters.length]; for (int i = 0 ; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this .parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null ) { continue ; } if (!this .resolvers.supportsParameter(parameter)) { throw new IllegalStateException (formatArgumentError(parameter, "No suitable resolver" )); } try { args[i] = this .resolvers.resolveArgument(parameter, mavContainer, request, this .dataBinderFactory); } catch (Exception ex) { if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; }
遍历判断获取支持解析该参数的参数解析器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Nullable private HandlerMethodArgumentResolver getArgumentResolver (MethodParameter parameter) { HandlerMethodArgumentResolver result = this .argumentResolverCache.get(parameter); if (result == null ) { for (HandlerMethodArgumentResolver resolver : this .argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this .argumentResolverCache.put(parameter, result); break ; } } } return result; }
解析参数的值通过调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法
2.4 数据响应与内容协商 返回值的处理逻辑
2.4.1 相关依赖的引入 创建springBoot项目的时候导入了spring-boot-starter-web依赖会自动的帮我们导入相关的json依赖,便于json数据的前后端传递
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
自动的帮我们导入相关的json的starter
1 2 3 4 5 6 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-json</artifactId > <version > 2.3.4.RELEASE</version > <scope > compile</scope > </dependency >
在spring-boot-starter-json中引入了jackson相关的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.11.2</version > <scope > compile</scope > </dependency > <dependency > <groupId > com.fasterxml.jackson.datatype</groupId > <artifactId > jackson-datatype-jdk8</artifactId > <version > 2.11.2</version > <scope > compile</scope > </dependency > <dependency > <groupId > com.fasterxml.jackson.datatype</groupId > <artifactId > jackson-datatype-jsr310</artifactId > <version > 2.11.2</version > <scope > compile</scope > </dependency > <dependency > <groupId > com.fasterxml.jackson.module</groupId > <artifactId > jackson-module-parameter-names</artifactId > <version > 2.11.2</version > <scope > compile</scope > </dependency >
返回值解析器的原理
1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
2、返回值处理器调用 handleReturnValue 进行处理
3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
利用 MessageConverters 进行处理 将数据写为json
1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
1、得到MappingJackson2HttpMessageConverter可以将对象写为json
2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
2.4.2 内容协商原理 引入jackson-dataformat-xml,测试返回是xml格式的数据
1 2 3 4 <dependency > <groupId > com.fasterxml.jackson.dataformat</groupId > <artifactId > jackson-dataformat-xml</artifactId > </dependency >
开启基于请求参数的内容协商功能
发请求: http://localhost:8080/test/person?format=json //指定格式是json
http://localhost:8080/test/person?format=xml //指定格式是xml
1 2 3 spring: contentnegotiation: favor-parameter: true
1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)
2、最终进行内容协商返回给客户端json即可
3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。
5、客户端需要【application/xml】。服务端能力【10种、json、xml】
6、进行内容协商的最佳匹配媒体类型
7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
2.4.3 自定义MessageConverter 1、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
2、Processor 处理方法返回值。通过 MessageConverter 处理
3、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
4、内容协商找到最终的 messageConverter;
步骤: 1、添加自定义的MessageConverter进系统底层
2、系统底层就会统计出所有MessageConverter能操作哪些类型
3、客户端内容协商 [guigu—>guigu]
编写自定义的的converter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package com.atguigu.boot.converter;import com.atguigu.boot.bean.Person;import org.springframework.http.HttpInputMessage;import org.springframework.http.HttpOutputMessage;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.HttpMessageNotReadableException;import org.springframework.http.converter.HttpMessageNotWritableException;import java.io.IOException;import java.io.OutputStream;import java.util.List;public class GuiguMessageConverter implements HttpMessageConverter <Person> { @Override public boolean canRead (Class<?> clazz, MediaType mediaType) { return false ; } @Override public boolean canWrite (Class<?> clazz, MediaType mediaType) { return clazz.isAssignableFrom(Person.class); } @Override public List<MediaType> getSupportedMediaTypes () { return MediaType.parseMediaTypes("application/x-guigu" ); } @Override public Person read (Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null ; } @Override public void write (Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { String data = person.getUserName()+";" +person.getAge()+";" +person.getBirth(); OutputStream body = outputMessage.getBody(); body.write(data.getBytes()); } }
在配置文件中加入自定义的converter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.atguigu.boot.config;import com.atguigu.boot.bean.Person;import com.atguigu.boot.bean.Pet;import com.atguigu.boot.converter.GuiguMessageConverter;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.convert.converter.Converter;import org.springframework.format.FormatterRegistry;import org.springframework.http.HttpInputMessage;import org.springframework.http.HttpOutputMessage;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.HttpMessageNotReadableException;import org.springframework.http.converter.HttpMessageNotWritableException;import org.springframework.util.StringUtils;import org.springframework.web.accept.HeaderContentNegotiationStrategy;import org.springframework.web.accept.ParameterContentNegotiationStrategy;import org.springframework.web.filter.HiddenHttpMethodFilter;import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import org.springframework.web.util.UrlPathHelper;import java.io.IOException;import java.io.OutputStream;import java.util.*;@Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { converters.add(new GuiguMessageConverter ()); } }; } }
在浏览器url上拼接参数的形式实现内容协商
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package com.atguigu.boot.config;import com.atguigu.boot.bean.Person;import com.atguigu.boot.bean.Pet;import com.atguigu.boot.converter.GuiguMessageConverter;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.convert.converter.Converter;import org.springframework.format.FormatterRegistry;import org.springframework.http.HttpInputMessage;import org.springframework.http.HttpOutputMessage;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.HttpMessageNotReadableException;import org.springframework.http.converter.HttpMessageNotWritableException;import org.springframework.util.StringUtils;import org.springframework.web.accept.HeaderContentNegotiationStrategy;import org.springframework.web.accept.ParameterContentNegotiationStrategy;import org.springframework.web.filter.HiddenHttpMethodFilter;import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import org.springframework.web.util.UrlPathHelper;import java.io.IOException;import java.io.OutputStream;import java.util.*;@Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void configureContentNegotiation (ContentNegotiationConfigurer configurer) { Map<String, MediaType> mediaTypes = new HashMap <>(); mediaTypes.put("json" ,MediaType.APPLICATION_JSON); mediaTypes.put("xml" ,MediaType.APPLICATION_XML); mediaTypes.put("gg" ,MediaType.parseMediaType("application/x-guigu" )); ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy (mediaTypes); HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy (); configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy)); } @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { converters.add(new GuiguMessageConverter ()); } }; } }
3.视图解析与模板引擎 视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染
3.1 视图解析
3.2 模板引擎-Thymeleaf 详细的使用教程: https://qingling.icu/posts/54835.html
3.2.1 Thymeleaf的使用 1.引入依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency >
2.编写测试controller
1 2 3 4 5 6 7 8 9 10 @Controller public class ViewTestController { @GetMapping("/view") public String hello (Model model) { model.addAttribute("keys" ,"Thymeleaf" ); return "success" ; } }
3.页面上渲染
1 2 3 4 5 6 7 8 9 10 <!DOCTYPE html > <html xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 th:text ="${keys}" > Hello</h1 > </body > </html >
3.3 后台管理系统注意点 templates目录的访问规则
templates目录下的所有页面资源只能通过请求访问到
表单重复提交的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.atguigu.admin.controller;@Slf4j @Controller public class IndexController { @GetMapping(value = {"/","/login"}) public String loginPage () { return "login" ; } @PostMapping("/login") public String main (String username, String password) { return "redirect:/main.html" ; } @GetMapping("/main.html") public String mainPage () { return "main" ; } }
4.拦截器 1.HandlerInterceptor接口 自定义拦截器需要实现的接口,以及需要实现接口中的方法
2.拦截器实现登录检查操作 登录检查的业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @Slf4j public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestURI = request.getRequestURI(); log.info("preHandle拦截的请求路径是{}" ,requestURI); HttpSession session = request.getSession(); Object loginUser = session.getAttribute("loginUser" ); if (loginUser != null ){ return true ; } request.setAttribute("msg" ,"请先登录" ); request.getRequestDispatcher("/" ).forward(request,response); return false ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("postHandle执行{}" ,modelAndView); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("afterCompletion执行异常{}" ,ex); } }
拦截器的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Configuration public class AdminWebConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor ()) .addPathPatterns("/**" ) .excludePathPatterns("/" ,"/login" ,"/css/**" ,"/fonts/**" ,"/images/**" ,"/js/**" ); } }
3.拦截器原理
1、根据当前请求,找到HandlerExecutionChain[可以处理请求的handler以及handler的所有 拦截器]
2、先来顺序执行 所有拦截器的 preHandle方法
1、如果当前拦截器prehandler返回为true,则执行下一个拦截器的preHandle
2、如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的afterCompletion方法
3、如果任何一个拦截器返回false,直接跳出不执行目标方法
4、所有拦截器都返回true,执行目标方法
5、倒序执行所有拦截器的postHandle方法
6、前面的步骤有任何异常都会直接倒序触发 afterCompletion
7、页面成功渲染完成以后,也会倒序触发 afterCompletion
5.文件上传 1.页面表单 1 2 3 4 <form method ="post" action ="/upload" enctype ="multipart/form-data" > <input type ="file" name ="file" > <br > <input type ="submit" value ="提交" > </form >
2.文件上传代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @PostMapping("/upload") public String upload (@RequestParam("email") String email, @RequestParam("username") String username, @RequestPart("headerImg") MultipartFile headerImg,//单个文件 @RequestPart("photos") MultipartFile[] photos) throws IOException { log.info("上传的信息:email={},username={},headerImg={},photos={}" , email, username, headerImg.getSize(), photos.length); if (!headerImg.isEmpty()) { String originalFilename = headerImg.getOriginalFilename(); headerImg.transferTo(new File ("H:\\cache\\" + originalFilename)); } if (photos.length > 0 ) { for (MultipartFile photo : photos) { if (!photo.isEmpty()) { String originalFilename = photo.getOriginalFilename(); photo.transferTo(new File ("H:\\cache\\" + originalFilename)); } } } return "main" ; }
3.MultipartAutoConfiguration 1 2 3 4 5 6 @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class }) @ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(MultipartProperties.class) public class MultipartAutoConfiguration {}
6.异常处理 开发中异常处理方法: https://qingling.icu/posts/31385.html
1.默认规则
默认情况下,Spring Boot提供/error
处理所有错误的映射
对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
2.自定义错误页面 templates/error/下的4xx,5xx页面会被自动解析
7.Web原生组件注入(Servlet、Filter、Listener) 7.1 使用Servlet Api 1.原生的Servlet的使用 1.编写自定义的servlet继承HttpServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.atguigu.boot.servlet;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@WebServlet(urlPatterns = "/my") public class MyServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("6666" ); } }
2.在启动类上添加自定义servlet的包扫描注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.atguigu.boot;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.web.servlet.ServletComponentScan;@ServletComponentScan("com.atguigu.boot") @SpringBootApplication public class Boot05Web01Application { public static void main (String[] args) { SpringApplication.run(Boot05Web01Application.class, args); } }
3.启动程序测试
2.原生的Filter的使用 1.编写自定义的Filter实现Filter接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.atguigu.boot.servlet;import lombok.extern.slf4j.Slf4j;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import java.io.IOException;@Slf4j @WebFilter(urlPatterns={"/css/*","/images/*","/my"}) public class MyFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { log.info("MyFilter初始化完成" ); } @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("MyFilter工作" ); chain.doFilter(request,response); } @Override public void destroy () { log.info("MyFilter销毁" ); } }
2.启动程序测试
3.原生的Listener的使用 1.编写自定义的MyServletContextListener实现ServletContextListener
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.atguigu.boot.servlet;import lombok.extern.slf4j.Slf4j;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.annotation.WebListener;@Slf4j @WebListener public class MyServletContextListener implements ServletContextListener { @Override public void contextInitialized (ServletContextEvent sce) { log.info("MyServletContextListener监听到项目初始化完成" ); } @Override public void contextDestroyed (ServletContextEvent sce) { log.info("MyServletContextListener监听到项目销毁" ); } }
2.启动程序测试
7.2 使用RegistrationBean 不使用@WebServlet、@WebFilter、@WebListener注解的方式(注释掉自定义servlet、Filter、Listener上的注解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.atguigu.boot.servlet;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.Arrays;@Configuration public class MyRegisterConfig { @Bean public ServletRegistrationBean myServlet () { MyServlet myServlet = new MyServlet (); return new ServletRegistrationBean (myServlet,"/my" ,"/my02" ); } @Bean public FilterRegistrationBean myFilter () { MyFilter myFilter = new MyFilter (); FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean (myFilter); filterRegistrationBean.setUrlPatterns(Arrays.asList("/my" ,"/css/*" )); return filterRegistrationBean; } @Bean public ServletListenerRegistrationBean myListener () { MyServletContextListener mySwervletContextListener = new MyServletContextListener (); return new ServletListenerRegistrationBean (mySwervletContextListener); } }
重新启动项目我们可以看到依然生效
8.嵌入式Servlet容器 8.1 切换嵌入式Servlet容器
8.2 定制Servlet容器 修改配置文件中以server.xxx打头的属性信息
三.数据访问 1.SQL 1.1 JDBC的使用 1.导入JDBC的场景
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jdbc</artifactId > </dependency >
2.导入自己使用的数据库的驱动(Mysql或者Oracle)版本号不写也可以,官方做了版本仲裁(但是要与实际安装的版本对应)
1 2 3 4 5 6 7 8 9 10 11 12 13 想要修改版本 1、直接依赖引入具体版本(maven的就近依赖原则) 2、重新声明版本(maven的属性的就近优先原则) <properties > <java.version > 1.8</java.version > <mysql.version > 5.1.45</mysql.version > </properties > 或者 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.45</version > </dependency >
3.添加配置
1 2 3 4 5 6 7 spring: datasource: url: jdbc:mysql://localhost:3306/db_account username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver
4.测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.atguigu.boot;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.jdbc.core.JdbcTemplate;@Slf4j @SpringBootTest class Boot05Web01ApplicationTests { @Autowired JdbcTemplate jdbcTemplate; @Test void contextLoads () { Long count = jdbcTemplate.queryForObject("select count(*) from `role` " , Long.class); log.info("记录数:{}" ,count); } }
1.2 分析自动配置 自动配置的类
DataSourceAutoConfiguration : 数据源的自动配置
修改数据源相关的配置:spring.datasource
数据库连接池的配置,是自己容器中没有DataSource才自动配置的
底层配置好的连接池是:HikariDataSource
DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
JndiDataSourceAutoConfiguration: jndi的自动配置
XADataSourceAutoConfiguration: 分布式事务相关的
四.单元测试 1.JUnit5的变化 Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入
JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行
JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
以前的版本
兼容以前的版本(junit4)需要额外导入的依赖(SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.junit.vintage</groupId > <artifactId > junit-vintage-engine</artifactId > <scope > test</scope > <exclusions > <exclusion > <groupId > org.hamcrest</groupId > <artifactId > hamcrest-core</artifactId > </exclusion > </exclusions > </dependency >
标注的注解
@SpringBootTest + @RunWith(SpringRunner.class)+@Test(4版本的@Test)
现在的版本
需要导入的依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency >
标注的注解
@SpringBootTest + @Test
两个不同版本的 @Test 注解
2.JUnit5常用注解 JUnit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
@Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
@ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
@RepeatedTest :表示方法可重复执行,下方会有详细介绍
@DisplayName :为测试类或者测试方法设置展示名称
@BeforeEach :表示在每个单元测试之前执行
@AfterEach :表示在每个单元测试之后执行
@BeforeAll :表示在所有单元测试之前执行
@AfterAll :表示在所有单元测试之后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package com.atguigu.boot;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.*;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.jdbc.core.JdbcTemplate;@Slf4j @DisplayName("Junit5Test测试类") @SpringBootTest public class Junit5Test { @Disabled @DisplayName("测试@DisplayName注解") @Test void testDisplayName () { log.info("@DisplayName注解的测试" ); } @BeforeEach void testBeforeEach () { log.info("执行了testBeforeEach()方法" ); } @AfterEach void testAfterEach () { log.info("执行了testAfterEach()方法" ); } @BeforeAll static void testBeforeAll () { log.info("执行了testBeforeAll()方法" ); } @AfterAll static void testAfterAll () { log.info("执行了testAfterAll()方法" ); } }
@Tag :表示单元测试类别,类似于JUnit4中的@Categories
@Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
@Timeout :表示测试方法运行如果超过了指定时间将会返回错误
1 2 3 4 5 6 7 8 @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) @Test void testTimeout () throws InterruptedException { Thread.sleep(600 ); }
@ExtendWith :为测试类或测试方法提供扩展类引用
1 2 3 4 5 6 7 8 @RepeatedTest(5) @Test void testRepeatedTest () { log.info("重复测试中" ); }
全部的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 package com.atguigu.boot;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.*;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.jdbc.core.JdbcTemplate;import java.util.concurrent.TimeUnit;@Slf4j @DisplayName("Junit5Test测试类") @SpringBootTest public class Junit5Test { @Disabled @DisplayName("测试@DisplayName注解") @Test void testDisplayName () { log.info("@DisplayName注解的测试" ); } @BeforeEach void testBeforeEach () { log.info("执行了testBeforeEach()方法" ); } @AfterEach void testAfterEach () { log.info("执行了testAfterEach()方法" ); } @BeforeAll static void testBeforeAll () { log.info("执行了testBeforeAll()方法" ); } @AfterAll static void testAfterAll () { log.info("执行了testAfterAll()方法" ); } @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) @Test void testTimeout () throws InterruptedException { Thread.sleep(600 ); } @RepeatedTest(5) @Test void testRepeatedTest () { log.info("重复测试中" ); } }
3.断言 断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告。
3.1 简单断言 用来对单个值进行简单的验证
方法
说明
assertEquals
判断两个对象或两个原始类型是否相等
assertNotEquals
判断两个对象或两个原始类型是否不相等
assertSame
判断两个对象引用是否指向同一个对象
assertNotSame
判断两个对象引用是否指向不同的对象
assertTrue
判断给定的布尔值是否为 true
assertFalse
判断给定的布尔值是否为 false
assertNull
判断给定的对象引用是否为 null
assertNotNull
判断给定的对象引用是否不为 null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test @DisplayName("简单断言") public void simple () { assertEquals(3 , 1 + 2 , "simple math" ); assertNotEquals(3 , 1 + 1 ); assertNotSame(new Object (), new Object ()); Object obj = new Object (); assertSame(obj, obj); assertFalse(1 > 2 ); assertTrue(1 < 2 ); assertNull(null ); assertNotNull(new Object ()); }
3.2 数组断言 通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
1 2 3 4 5 @Test @DisplayName("数组断言") public void array () { assertArrayEquals(new int []{1 , 2 }, new int [] {1 , 2 }); }
3.3 组合断言 assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
1 2 3 4 5 6 7 8 @Test @DisplayName("组合断言") public void all () { assertAll("Math" , () -> assertEquals(2 , 1 + 1 ), () -> assertTrue(1 > 0 ) ); }
3.4 异常断言 在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。
1 2 3 4 5 6 7 8 @Test @DisplayName("异常测试") public void exceptionTest () { ArithmeticException exception = Assertions.assertThrows( ArithmeticException.class, () -> System.out.println(1 % 0 )); }
3.5 超时断言 Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间
1 2 3 4 5 6 @Test @DisplayName("超时测试") public void timeoutTest () { Assertions.assertTimeout(Duration.ofMillis(1000 ), () -> Thread.sleep(500 )); }
3.6 快速失败 通过 fail 方法直接使得测试失败
1 2 3 4 5 @Test @DisplayName("快速失败") public void shouldFail () { fail("This should fail" ); }
全部代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 package com.atguigu.admin;import org.junit.jupiter.api.*;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.jdbc.core.JdbcTemplate;import java.util.concurrent.TimeUnit;import static org.junit.jupiter.api.Assertions.*;@DisplayName("junit5功能测试类") public class Junit5Test { @DisplayName("简单断言") @Test void testSimpleAssertions () { int cal = cal(3 , 2 ); assertEquals(6 , cal, "业务逻辑计算失败" ); Object obj1 = new Object (); Object obj2 = new Object (); assertSame(obj1, obj2, "两个对象不一样" ); } @Test @DisplayName("数组断言") void array () { assertArrayEquals(new int []{1 , 2 }, new int []{1 , 2 }, "数组内容不相等" ); } @Test @DisplayName("组合断言") void all () { assertAll("test" , () -> assertTrue(true && true , "结果不为true" ), () -> assertEquals(1 , 2 , "结果不是1" )); System.out.println("=====" ); } @DisplayName("异常断言") @Test void testException () { assertThrows(ArithmeticException.class, () -> { int i = 10 / 2 ; }, "业务逻辑居然正常运行?" ); } @DisplayName("快速失败") @Test void testFail () { if (1 == 2 ){ fail("测试失败" ); } } int cal (int i, int j) { return i + j; } }
4.前置条件 JUnit 5 中的前置条件(assumptions[假设])类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @DisplayName("前置条件") public class AssumptionsTest { private final String environment = "DEV" ; @Test @DisplayName("simple") public void simpleAssume () { assumeTrue(Objects.equals(this .environment, "DEV" )); assumeFalse(() -> Objects.equals(this .environment, "PROD" )); } @Test @DisplayName("assume then do") public void assumeThenDo () { assumingThat( Objects.equals(this .environment, "DEV" ), () -> System.out.println("In DEV" ) ); } }
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止
assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象
只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止
5.嵌套测试 JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 @DisplayName("A stack") class TestingAStackDemo { Stack<Object> stack; @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew () { new Stack <>(); } @Nested @DisplayName("when new") class WhenNew { @BeforeEach void createNewStack () { stack = new Stack <>(); } @Test @DisplayName("is empty") void isEmpty () { assertTrue(stack.isEmpty()); } @Test @DisplayName("throws EmptyStackException when popped") void throwsExceptionWhenPopped () { assertThrows(EmptyStackException.class, stack::pop); } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked () { assertThrows(EmptyStackException.class, stack::peek); } @Nested @DisplayName("after pushing an element") class AfterPushing { String anElement = "an element" ; @BeforeEach void pushAnElement () { stack.push(anElement); } @Test @DisplayName("it is no longer empty") void isNotEmpty () { assertFalse(stack.isEmpty()); } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped () { assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test @DisplayName("returns the element when peeked but remains not empty") void returnElementWhenPeeked () { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); } } } }
6.参数化测试 参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @ParameterizedTest @ValueSource(strings = {"one", "two", "three"}) @DisplayName("参数化测试") public void parameterizedTest (String string) { System.out.println(string); Assertions.assertTrue(StringUtils.isNotBlank(string)); } @ParameterizedTest @MethodSource("method") @DisplayName("方法来源参数") public void testWithExplicitLocalMethodSource (String name) { System.out.println(name); Assertions.assertNotNull(name); } static Stream<String> method () { return Stream.of("apple" , "banana" ); }
7.迁移指南 在进行迁移(Junit4迁移到Junit5)的时候需要注意如下的变化:
注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
把@Before 和@After 替换成@BeforeEach 和@AfterEach。
把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
把@Ignore 替换成@Disabled。
把@Category 替换成@Tag。
把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。
五.指标监控 1.SpringBoot Actuator 未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能
1.1 如何使用 1.引入依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency >
2.测试访问 http://localhost:8080/actuator/ endpoint节点
3.设置以web的方式访问所有的端点
1 2 3 4 5 6 management: endpoints: enabled-by-default: true web: exposure: include: '*'
设置之后就可以通过web(访问 http://localhost:8080/actuator/beans)的方式访问beans端点了
支持的暴露方式
是通过web还是jmx方式访问的端点一览表
ID
JMX
Web
auditevents
Yes
No
beans
Yes
No
caches
Yes
No
conditions
Yes
No
configprops
Yes
No
env
Yes
No
flyway
Yes
No
health
Yes
Yes
heapdump
N/A
No
httptrace
Yes
No
info
Yes
Yes
integrationgraph
Yes
No
jolokia
N/A
No
logfile
N/A
No
loggers
Yes
No
liquibase
Yes
No
metrics
Yes
No
mappings
Yes
No
prometheus
N/A
No
scheduledtasks
Yes
No
sessions
Yes
No
shutdown
Yes
No
startup
Yes
No
threaddump
Yes
No
1.2 常用的EndPoints http://localhost:8080/actuator/endPoint
ID
描述
auditevents
暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
。
beans
显示应用程序中所有Spring Bean的完整列表。
caches
暴露可用的缓存。
conditions
显示自动配置的所有条件信息,包括匹配或不匹配的原因。
configprops
显示所有@ConfigurationProperties
。
env
暴露Spring的属性ConfigurableEnvironment
flyway
显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway
组件。
health
显示应用程序运行状况信息。
httptrace
显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository
组件。
info
显示应用程序信息。
integrationgraph
显示Spring integrationgraph
。需要依赖spring-integration-core
。
loggers
显示和修改应用程序中日志的配置。
liquibase
显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase
组件。
metrics
显示当前应用程序的“指标”信息。
mappings
显示所有@RequestMapping
路径列表。
scheduledtasks
显示应用程序中的计划任务。
sessions
允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
shutdown
使应用程序正常关闭。默认禁用。
startup
显示由ApplicationStartup
收集的启动步骤数据。需要使用SpringApplication
进行配置BufferingApplicationStartup
。
threaddump
执行线程转储。
如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
ID
描述
heapdump
返回hprof
堆转储文件。
jolokia
通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
。
logfile
返回日志文件的内容(如果已设置logging.file.name
或logging.file.path
属性)。支持使用HTTPRange
标头来检索部分日志文件的内容。
prometheus
以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus
。
最常用的Endpoint
Health:监控状况
Metrics:运行时指标
Loggers:日志记录
1.3 Health Endpoint 健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
很多的健康检查默认已经自动配置好了,比如:数据库、redis等
可以很容易的添加自定义的健康检查机制
设置显示health端点的详细信息
1 2 3 4 management: endpoint: health: show-details: always
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 { "status" : "UP" , "components" : { "db" : { "status" : "UP" , "details" : { "database" : "MySQL" , "validationQuery" : "isValid()" } } , "diskSpace" : { "status" : "UP" , "details" : { "total" : 510769754112 , "free" : 342642921472 , "threshold" : 10485760 , "exists" : true } } , "ping" : { "status" : "UP" } } }
1.4 Metrics Endpoint 提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;
通过Metrics对接多种监控系统
简化核心Metrics开发
添加自定义Metrics或者扩展已有Metrics
查看完整的监控指标信息 http://localhost:8080/actuator/metrics
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 { "names" : [ "hikaricp.connections" , "hikaricp.connections.acquire" , "hikaricp.connections.active" , "hikaricp.connections.creation" , "hikaricp.connections.idle" , "hikaricp.connections.max" , "hikaricp.connections.min" , "hikaricp.connections.pending" , "hikaricp.connections.timeout" , "hikaricp.connections.usage" , "http.server.requests" , "jdbc.connections.max" , "jdbc.connections.min" , "jvm.buffer.count" , "jvm.buffer.memory.used" , "jvm.buffer.total.capacity" , "jvm.classes.loaded" , "jvm.classes.unloaded" , "jvm.gc.live.data.size" , "jvm.gc.max.data.size" , "jvm.gc.memory.allocated" , "jvm.gc.memory.promoted" , "jvm.gc.pause" , "jvm.memory.committed" , "jvm.memory.max" , "jvm.memory.used" , "jvm.threads.daemon" , "jvm.threads.live" , "jvm.threads.peak" , "jvm.threads.states" , "logback.events" , "process.cpu.usage" , "process.start.time" , "process.uptime" , "system.cpu.count" , "system.cpu.usage" ] }
查看某一个监控指标的详细信息(例如查看hikaricp.connections的详细信息) http://localhost:8080/actuator/metrics/hikaricp.connections
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "name" : "hikaricp.connections" , "description" : "Total connections" , "baseUnit" : null , "measurements" : [ { "statistic" : "VALUE" , "value" : 10 } ] , "availableTags" : [ { "tag" : "pool" , "values" : [ "HikariPool-1" ] } ] }
1.5 管理Endpoint 开启与禁用Endpoints
默认所有的Endpoint除过shutdown都是开启的。
需要开启或者禁用某个Endpoint 配置模式为 management.endpoint.endpointName.enabled = true
1 2 3 4 management: endpoint: beans: enabled: true
或者禁用所有的Endpoint然后手动开启指定的Endpoint
1 2 3 4 5 6 7 8 management: endpoints: enabled-by-default: false endpoint: beans: enabled: true health: enabled: true
1.6 定制Endpoint 1.6.1 定制health endpoint 1.编写MyComHealthIndicator类继承AbstractHealthIndicator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.atguigu.boot.acutuator.health;import org.springframework.boot.actuate.health.AbstractHealthIndicator;import org.springframework.boot.actuate.health.Health;import org.springframework.boot.actuate.health.Status;import org.springframework.stereotype.Component;import java.util.HashMap;import java.util.Map;@Component public class MyComHealthIndicator extends AbstractHealthIndicator { @Override protected void doHealthCheck (Health.Builder builder) throws Exception { Map<String,Object> map = new HashMap <>(); if (1 == 1 ){ builder.status(Status.UP); map.put("count" ,1 ); map.put("ms" ,100 ); }else { builder.status(Status.OUT_OF_SERVICE); map.put("err" ,"连接超时" ); map.put("ms" ,3000 ); } builder.withDetail("code" ,100 ) .withDetails(map); } }
myCom = MyComHealthIndicator - HealthIndicator 所以类的名字一定要以HealthIndicator为后缀
1.6.2 定制 info endpoint 方式一: 编写配置文件
1 2 3 4 5 info: appName: boot-admin version: 2.0 .1 mavenProjectName: @project.artifactId@ mavenProjectVersion: @project.version@
方式二: 编写InfoContributor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.atguigu.boot.acutuator.info;import org.springframework.boot.actuate.info.Info;import org.springframework.boot.actuate.info.InfoContributor;import org.springframework.stereotype.Component;import java.util.Collections;@Component public class AppInfoInfoContributor implements InfoContributor { @Override public void contribute (Info.Builder builder) { builder.withDetail("msg" ,"你好" ) .withDetail("hello" ,"atguigu" ) .withDetails(Collections.singletonMap("world" ,"666600" )); } }
如果我们没有删除上面配置文件的配置,这时我们访问 http://localhost:8080/actuator/info 的时候会将配置文件和InfoContributor中的信息共同返回
1.6.3 定制Metrics信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class MyService { Counter counter; public MyService (MeterRegistry meterRegistry) { counter = meterRegistry.counter("myservice.method.running.counter" ); } public void hello () { counter.increment(); } } @Bean MeterBinder queueSize (Queue queue) { return (registry) -> Gauge.builder("queueSize" , queue::size).register(registry); }
1.7 新增Endpoint 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.atguigu.boot.acutuator.endpoint;import org.springframework.boot.actuate.endpoint.annotation.Endpoint;import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;import org.springframework.stereotype.Component;import java.util.Collections;import java.util.Map;@Component @Endpoint(id = "myservice") public class MyServiceEndPoint { @ReadOperation public Map getDockerInfo () { return Collections.singletonMap("dockerInfo" ,"docker started....." ); } @WriteOperation public void stopDocker () { System.out.println("docker stopped....." ); } }
1.8 可视化 github地址: https://github.com/codecentric/spring-boot-admin
项目文档: https://docs.spring-boot-admin.com/current/getting-started.html
1.创建一个新的springBoot项目用作服务端(场景只需要选择web场景即可)
2.引入依赖
1 2 3 4 5 <dependency > <groupId > de.codecentric</groupId > <artifactId > spring-boot-admin-starter-server</artifactId > <version > 2.3.1</version > </dependency >
3.在启动类上添加注解
4.修改以下端口号以防和业务的端口冲突
5.访问http://localhost:8888
6.客户端上引入依赖
1 2 3 4 5 <dependency > <groupId > de.codecentric</groupId > <artifactId > spring-boot-admin-starter-client</artifactId > <version > 2.3.1</version > </dependency >
7.客户端的配置文件添加以下的配置
1 2 spring.boot.admin.client.url =http://localhost:8888 #服务端的地址 management.endpoints.web.exposure.include =*
8.重启一下客户端,然后访问服务端的localhost:8080
点击应用墙,点击需要查看信息的应用就可以看到应用的详细信息
六.原理解析 1.Profile功能 为了方便多环境适配,springboot简化了profile功能
@Profile注解
外部配置源
常用:Java属性文件、YAML文件、环境变量、命令行参数;
配置文件查找位置
下面的优先级覆盖上面的优先级
(1) classpath 根路径
(2) classpath 根路径下config目录
(3) jar包当前目录
(4) jar包当前目录的config目录
(5) /config子目录的直接子目录
配置文件加载顺序:
当前jar包内部的application.properties和application.yml
当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
引用的外部jar包的application.properties和application.yml
引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
总结: 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项