1.第一个SpringBoot程序
1、创建一个新项目
2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
3、填写项目信息
4、选择初始化的组件(初学勾选 Web 即可)
5、填写项目路径
6、等待项目构建成功
项目结构分析:
通过上面步骤完成了基础项目的创建。就会自动生成以下文件。
1、程序的主启动类
2、一个 application.properties 配置文件
3、一个 测试类
4、一个 pom.xml
编写一个http接口
1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到
2、在包中新建一个HelloController类
//自动装配 @RestController public class HelloSpringBoot { //接口http://localhost:8080/hello @RequestMapping("/hello") public String hello(){ //调用业务,接受前端参数 return "Hello,Word"; } }
3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!
在application.properties中修改端口号
#更改项目端口号 server.port=8081
2.自动配置原理
1.组件添加
1.@Configuration
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件 public class MyConfig {}
2.@Bean、@Component、@Controller、@Service、@Repository
3.@ComponentScan、@Import
@Import({User.class, DBHelper.class}) //给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名 @Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件 public class MyConfig { }
4.@Conditional
条件装配:满足Conditional指定的条件,则进行组件注入
2.配置绑定
1.@ConfigurationProperties
@Component+@ConfigurationProperties
prefix 声明我们的配置类跟配置文件哪个前缀下的属性一一绑定
实体类
@Data @NoArgsConstructor @AllArgsConstructor @Component //声明这是一个组件 @ConfigurationProperties(prefix = "mycar") public class Car { private String brand; private Integer price; }
controller层
@Autowired //自动装配 Car car; @RequestMapping("/car") public Car car(){ return car; }
application.properties
mycar.brand = BYD mycar.price = 100000
访问localhost:8080/car可以获取到application.properties
中的数据
@ConfigurationProperties+@EnableConfigurationProperties
@EnableConfigurationProperties可以将没有@Component的第三方导入类自动注册到容器中
实体类
@Data @NoArgsConstructor @AllArgsConstructor @ConfigurationProperties(prefix = "mycar") public class Car { private String brand; private Integer price; }
配置类
@Configuration(proxyBeanMethods = false) //告诉springboot这是一个配置类 == 配置文件 @EnableConfigurationProperties(Car.class) //1.开启Car配置绑定功能 //2.把这个Car这个组件自动注入到容器中 public class MyConfig { }
3.自动配置原理
1.引导加载自动配置类
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication {}
1.@SpringBootConfiguration
@Configuration 代表当前是一个配置类
2.@ComponentScan
指定扫描哪些包
3.@EnableAutoConfiguration
@AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration {}
1.@AutoConfigurationPackage
自动配置包
@Import({Registrar.class}) //给容器中导入一个组件 public @interface AutoConfigurationPackage {} //利用Registrar给容器中导入一系列组件 //将指定的一个包下的所有组件导入进来?Springboot01Application主启动类所在的包下
2.@Import({AutoConfigurationImportSelector.class})
1.利用getAutoConfigurationEntry(AnnotationMetadata annotationMetadata);给容器中导入一些组件 2.调用List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);获取到所有需要导入到容器中的配置类 3.利用工厂加载Map<String, List<String>> loadSpringFactories(ClassLoader classLoader);得到所有的组件 4.从META-INF/spring.factories位置来加载一个文件 默认扫描我们当前系统里面所有的META-INF/spring.factories位置的文件 spring-boot-autoconfigure-2.4.4.jar里面也有META-INF/spring.factories
文件里面写死了spring-boot一启动就要给容器中加载所有的配置类 # spring-boot-autoconfigure-2.4.4.jar/META-INF/spring.factories org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\ org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\ org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\ org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\ org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\ org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\ org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\ org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\ org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
2.按需开启自动配置项
虽然我们130个场景的所有自动配置启动的时候默认全部加载 按照条件装配规则(@Conditional),但最终会按需配置
3.修改默认配置
@Bean @ConditionalOnBean({MultipartResolver.class}) //容器中有这个组件 @ConditionalOnMissingBean( name = {"multipartResolver"} //容器中没有这个名字 multipartResolver ) public MultipartResolver multipartResolver(MultipartResolver resolver) { //给@Bean标注的方法传入了对象参数,这个参数的值就会在容器中找 //SpringMVC multipartResolver防止用户自己配置的文件上传解析器不符合规范 return resolver; } //给文件中加入了上传解析器
SpringBoot默认会在底层配好所有的组件,但是如果用户配置了,以用户配置的优先
@Bean @ConditionalOnMissingBean //没有这个组件 public CharacterEncodingFilter characterEncodingFilter() {}
总结:
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效,默认都会绑定一个配置文件指定的值,xxxxProperties里面拿,xxxxProperties和配置文件进行了绑定
- 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于这些功能就有了
- 定制化配置
- 用户直接自己@Bean替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改
3.SpringBoot核心功能
1.yaml
简介
YAML是"YAML Ain't Markup Language"(YAML不是一种标记语言)的递归缩写,在开发的这种语言时,YAML的意思其实是:"Yet Another Markup Language" (仍是一种标记语言)
非常适合用来做以数据为中心的配置文件
基本语法
- key:value;kv之间有空格
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许使用空格
- 缩进的空格数不重要,只要在相同层级的元素左对齐即可
- "#"表示注释
- 字符串无需加引号,''与""表示字符串内容,会被 转义/不转义
数据类型
字面量:单个,不可再分的值。date、boolean、String、number、null
k: V
对象:键值对的集合。map、hash、set、object
#行类写法: k: {k1:v1,k2:v2,k3:v3} #或 k: k1: v1 k2: v2 k3: v3
数组:一组按次序排列的值。array、list、queue
#行内写法: k: [v1,v2,v3] #或 k: - v1 - v2 - v3
实例
java实体类
@Data @AllArgsConstructor @NoArgsConstructor @Component @ConfigurationProperties(prefix = "person") public class Person { private String userName; private Boolean boss; private Date birth; private Integer age; private Pet pet; private String[] interests; private List<String> list; private Map<String,Object> score; private Set<Double> salarys; private Map<String,List<Pet>> allPets; } //=============================================== @Data @AllArgsConstructor @NoArgsConstructor public class Pet { private String name; private Double weight; }
yaml语法
person: user-name: 小王 boss: true birth: 2001/10/26 age: 20 interests: [游戏,篮球] list: - 猫 - 狗 # score: # english: 90 # math: 100 # chinese: 80 score: {english:90,math:100,chinese:80} salarys: - 4800.98 - 4900.85 pet: name: 旺财 weight: 99.85 allPets: sick: - {name: 唐先林,weight: 90.99} - name: 肖孝鹏 weight: 80.99 health: - {name: 刘高,weight: 188.96}
如果需要提示在maven中添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
这样关联的实体类在yaml文件配置的时候有提示
2.web开发
1.静态资源访问
1.静态资源目录
只要静态资源都访问在类路径下:/static
or/public
or/resources
or/META-INF/resources
访问:当前项目根路径/+静态资源名
原理:静态映射/**
请求进去,先去找controller看能不能处理。不能处理的所有请求交给静态资源处理器。静态资源也找到404
2.静态资源访问前缀
默认无前缀
spring: mvc: static-path-pattern: /res/**
配置这个后,所有静态资源访问都需要加上这个前缀/res+/静态资源文件名
2.欢迎页支持
静态资源路径下index.html
- 可以配置静态资源路径
- 但是不可以配置静态资源访问前缀,否则导致index.html不能被默认访问
3.自定义favicon
将图片重命名为favicon.ico,放在静态资源目录下
配置静态资源访问前缀也会使favicon失效
4.静态资源配置原理
- SpringBoot启动默认加载. xxxAutoConfiguration类(自动配置类)
- SpringMVC功能的自动配置类WebMvcAutoConfiguration
@Configuration( proxyBeanMethods = false ) @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration {}
- 给容器配了啥
@Configuration( proxyBeanMethods = false ) @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class}) @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class, WebProperties.class}) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
- 配置文件的相关属性和xxx进行了绑定WebMvcProperties == spring.mvc、ResourceProperties == spring.resources、WebProperties == spring.web
1.配置类只有一个有参构造器
//有参构造器所有参数的值都会从容器中确定 public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConvertersProvider = messageConvertersProvider; this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this.dispatcherServletPath = dispatcherServletPath; this.servletRegistrations = servletRegistrations; this.mvcProperties.checkConfiguration(); }
2.资源处理的默认规则
protected void addResourceHandlers(ResourceHandlerRegistry registry) { super.addResourceHandlers(registry); if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); } else { ServletContext servletContext = this.getServletContext(); //webjars的规则 this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/"); this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this.resourceProperties.getStaticLocations()); if (servletContext != null) { registration.addResourceLocations(new Resource[]{new ServletContextResource(servletContext, "/")}); } }); } }
spring: web: resources: add-mappings: false #禁用所有静态资源规则
3.欢迎页处理规则
//HandlerMapping:处理器映射,保存了每一个Handler能处理哪些请求 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider)); welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations()); return welcomePageHandlerMapping; }
5.请求参数处理
1.请求映射
- @RequestMapping
- Rest风格支持(使用Http请求方式动词来表示对资源的操作)
- 以前:/getUser 获取用户 /deleteUser 删除用户 /saveUser 保存用户
- 现在:/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
- 核心Filter:HiddenHttpMethodFilter
- 用法:表单method=post,隐藏域_method=put
@RestController public class TestMethod { @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-张三"; } }
表单提交
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/user" method="get"> <input type="submit" value="GET"> </form> <form action="/user" method="post"> <input type="submit" value="POST"> </form> <form action="/user" method="post"> <!--需要添加这个隐藏域--> <input name="_method" hidden value="PUT"> <input type="submit" value="PUT"> </form> <form action="/user" method="post"> <input name="_method" hidden value="DELETE"> <input type="submit" value="DELETE"> </form> </body> </html>
spring: mvc: hiddenmethod: filter: enabled: true
注意:默认的为false,我们需要开启改为true
Rest原理(表单提交要使用REST的时候)
- 表单提交会带上_method=PUT
- 请求过来被HiddenHttpMethodFilter拦截
- 请求是否正常,并且是POST
- 获取到_method的值
- 兼容以下PUT,DELETE,PATCH
- 原生request(post),包装模式requestWrapper重写了getMethod方法,返回的是传入的值
- 过滤器链放行的时候用wrapper,以后的方法调用getMethod是requestWrapper的
- 请求是否正常,并且是POST
Rest客户端工具
- 如果客户端工具发送put、delete,等方式请求,无需filter,则不需要开启上面的配置
如果我们需要修改默认的_method
我们自己写一个配置类
@Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter(){ HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter(); filter.setMethodParamName("_m"); return filter; } }
<form action="/user" method="post"> <!--需要添加这个隐藏域--> <input name="_m" hidden value="PUt"> <input type="submit" value="PUT"> </form>
就可以修改了
2.请求映射原理
DispatcherServlet -->doDispatch()
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); // 找到当前请求使用哪个handler(controller的方法)处理 mappedHandler = getHandler(processedRequest); //handlerMapping : 处理器映射
RequestMappingHandlerMapping:保存了所有的HandlerMapping和handler的映射规则
所有的请求映射都在HandlerMapping中
- SpringBoot自动配置欢迎页的WelcomePageHandlerMapping。能访问到index.html
- SpringBoot自动配置了默认的RequestMappingHandlerMapping
- 请求进来,挨个尝试所有的HandlerMapping是否有请求信息
- 如果有就找到这个请求对应得handler
- 如果没有就是下一个HandlerMapping
- 我们需要一些自定义的映射请求,我们也可以给容器中方HandlerMapping,自定义HandlerMapping
6.普通参数与基本注解
@PathVariable(路径变量)
public class ParameterTestController { @GetMapping("/car/{id}/join/{username}") public Map<String,Object> getCar(@PathVariable("id") Integer id, @PathVariable("username") String name, @PathVariable Map<String,Object> pv){ Map<String,Object> map = new HashMap<>(); map.put("id",id); map.put("username",name); map.put("pv",pv); return map; } }
@RequestHeader(获取请求头)
@RestController public class ParameterTestController { @GetMapping("/car/{id}/join/{username}") public Map<String,Object> getCar(@RequestHeader("User-Agent")String useraAgent, @RequestHeader Map<String,String> header){ Map<String,Object> map = new HashMap<>(); map.put("User-Agent",useraAgent); map.put("Header",header); return map; } }
@RequestParam(获取请求参数)
@RestController public class ParameterTestController { @GetMapping("/car/{id}/join/{username}") public Map<String,Object> getCar(@RequestParam("age") Integer age, @RequestParam("inters")List<String> inters, @RequestParam Map<String,String> parms){ Map<String,Object> map = new HashMap<>(); map.put("age",age); map.put("inters",inters); map.put("parms",parms); return map; } }
<a href="/car/3/join/唐先林?age=18&inters=basketball&inters=game">点我</a>
@CookieValue(获取Cookie值)
@RestController public class ParameterTestController { @RequestMapping("/cookie") public Map<String,Object> getCar( @CookieValue("_ga") String _ga, @CookieValue("_ga") Cookie cookie){ Map<String,Object> map = new HashMap<>(); map.put("_ga",_ga); System.out.println("cookie"+cookie); return map; } }
@RequestAttribute(获取request域属性)
@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("msg") String msg, @RequestAttribute("code") Integer code, HttpServletRequest request){ Object msg1 = request.getAttribute("msg"); Map<String,Object> map = new HashMap<>(); map.put("request_msg",msg1); map.put("anntation_msg",msg); return map; } }
@RequestBody(获取请求体)
@RestController public class ParameterTestController { @PostMapping("/save") public Map postMethod(@RequestBody String content){ Map<String,Object> map = new HashMap<>(); map.put("content",content); return map; } }
<form action="/save" method="post"> 账号: <input name="username"> 密码: <input name="password"> <input type="submit" value="提交"> </form>
@MatrixVariable(矩阵变量)
请求路径:http://localhost:8080/cars/sell;low=34;brand=byd,yd
//1.语法:/cars/sell;low=34;brand=byd,yd //2.Springboot默认禁用了矩阵变量的功能 //3.手动开启:原理,对于路径的处理,都是UrlPathHelper进行解析的, // UrlPathHelper中的removeSemicolonContent(移除分号内容)这个属性就是用来支持矩阵变量的 @ResponseBody @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; }
默认禁用矩阵功能需要开启
@Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); //不移除;后面的内容,矩阵变量功能就可以生效 urlPathHelper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(urlPathHelper); } }; }
请求路径:http://localhost:8080/boss/1;age=20/2;age=10
// /boss/1;age=20/2;age=10 //矩阵变量必须有url路径变量才能被解析 @ResponseBody @GetMapping("/boss/{bossId}/{empId}") //pathVar指定获取age的路径,不然默认获取都是第一个 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("boosAge",bossAge); map.put("empAge",empAge); return map; }
<a href="/boss/1;age=20/2;age=10">矩阵变量</a>
7.参数处理原理
- HandlerMapping中找到能处理请求的Handler(Controller.method())
- 为当前Handler找一个适配器HandlerAdapter;RequestMappingHandlerAdapter
1.HandlerAdapter
0-支持方法上标注@RequestMapping
1-支持函数式编程的
2.执行目标方法
// Actually invoke the handler. //DispatcherServlet --> doDispatch mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法 //ServletInvocableHandlerMethod Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); //获取方法参数值 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
3.参数解析器
确定将要执行的目标方法的每一个参数的值是什么
SpringMVC目标方法能写多少种参数类型,取决于参数解析器
- 当前解析器是否支持解析这种参数
- 支持就调用 resolveArgument
4.返回值处理器
5.如何确定目标方法每一个参数值
==========InvocableHandlerMethod============ 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) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; }
挨个判断所有参数解析器哪个支持解析这个参数
@Nullable private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { //增强for循环遍历参数解析器 for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this.argumentResolverCache.put(parameter, result); //放到缓存中 break; } } } return result; }
解析这个参数的值
8.复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、
RedirectAttributes(重定向携带数据)、ServletResponse(response)
Map<String,Object> map,Model model,HttpServletRequest request, //都是可以给request域中放数据 request.getAttribute(); //在request中可以获取这些数据
Map,Model类型的参数,会返回 mavContainer.getModel() ; -->BindingAwareModelMap 是Model也是Map
目标方法执行完成
将所有数据都放在ModelAndViewContainer中;包含要去的页面地址View,还包含Model数据
处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
InternalResourceView @Override protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any. exposeHelpers(request); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } // If already included or response already committed, perform include, else forward. if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including [" + getUrl() + "]"); } rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to [" + getUrl() + "]"); } rd.forward(request, response); } }
//暴露模型作为请求域属性 // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception { //model中的所有数据挨个遍历放在请求域中 model.forEach((name, value) -> { if (value != null) { request.setAttribute(name, value); } else { request.removeAttribute(name); } }); }
9.自定义对象参数
pojo的封装过程
-
ServletModelAttributeMethodProcessor 这个参数处理器支持
-
是否为简单类型
public static boolean isSimpleValueType(Class<?> type) { return (Void.class != type && void.class != type && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type)); }
-
@Override @Nullable public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer"); Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory"); String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null) { mavContainer.setBinding(name, ann.binding()); } Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { // Create attribute instance try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { if (isBindExceptionRequired(parameter)) { // No BindingResult parameter -> fail with BindException throw ex; } // Otherwise, expose null/empty value and associated BindingResult if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } else { attribute = ex.getTarget(); } bindingResult = ex.getBindingResult(); } } if (bindingResult == null) { // Bean property binding and validation; // skipped in case of binding failure on construction. WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Value type adaptation, also covering java.util.Optional if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
-
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder:web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
-
WebDataBinder利用它里面的Converters将数据转成指定的数据类型,再次封装到JavaBean中
-
GenerConversionService:在设置每一个值的时候,找到里面所有的converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean -- Integer)
自定义converter
把String转换成Pets对象
@Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter<String, Pets>() { @Override public Pets convert(String s) { if (!StringUtils.isEmpty(s)){ Pets pets = new Pets(); String[] split = s.split(","); pets.setName(split[0]); pets.setAge(Integer.parseInt(split[1])); return pets; } return null; } }); }
前端传的数据
<form action="/saveuser" method="post"> 姓名: <input name="userName" value="zhangsan"/> <br/> 年龄: <input name="age" value="18"/> <br/> 生日: <input name="birth" value="2000/05/21"/> <br/> 宠物: <input name="pet" value="阿猫,3"/> <input type="submit" value="保存"> </form>
Controller处理请求
//数据绑定:页面提交的请求数据(GET,POST)都可以和对象属性进行绑定 @ResponseBody @PostMapping("/saveuser") public Pepole saveUser(Pepole pepole){ return pepole; }
10.数据响应
try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); }
@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); }
有@ModelAttribute和@ResponseBody注解 --> RequestResponseBodyMethodProcessor
1.HttpMessageConverter原理
1.MessageConverter规范
HttpMessageConverter:看是否支持将class类型的对象,转为MediaType类型的数据
例子:Person对象转为JSON,或者JSON转为Person
2.默认的MessageConverter
3.最终MappingJackson2HttpMessageConverter把对象转为JSON(利用底层的jackson的objectMapper转换的)
返回值解析器原理
//RequestResponseBodyMethodProcessor @Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // Try even with null return value. ResponseBodyAdvice could get involved. //使用消息转换器进行写出操作 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
- 1.返回值处理器判断是否支持这种类型返回值supportsReturnType
- 2.返回值处理器调用handleReturnValue进行处理
- 3.RequestResponseBodyMethodProcessor可以处理返回值标了@ResponseBody注解的
- 1.利用MessageConverters进行处理,将数据写为json
- 1.内容协商(浏览器默认会以请求头的方式告诉服务器他能接收什么样的内容类型)
- 2.服务器最终根据自身的能力,决定服务器能生产出什么样的内容类型的数据
- 3.SpringMVC会挨个遍历所有容器底层的HttpMessageConverter,看谁能处理?
- 1.得到MappingJackson2HttpMessageConverter可以将对象写为JSON
- 2.利用MappingJackson2HttpMessageConverter将对象转为JSON在写出去
- 1.利用MessageConverters进行处理,将数据写为json
2.内容协商
根据客户端接收能力不同,返回不同媒体类型的数据
1.导入依赖
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
3.开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能
spring: mvc: contentnegotiation: favor-parameter: true #开启请求参数内容协商模式
发请求:http://localhost:8080/test/pepole?format=xml
format=?后面接的就是返回参数类型的格式
确定客户端接收什么样的内容类型:
1.Parameter策略优先确定是要返回XML数据(获取请求头中的format的值)
4.内容协商原理
- 判断当前响应头中是否已经有明确的媒体类型。MediaType
- 获取客户端支持接收的内容类型。(获取客户端的Accept请求头字段)
- 遍历循环所有当前系统的MessageConverter,看谁支持操作这个对象(Pepole)
- 找到支持操作Pepole的converter,把converter支持的媒体类型统计出来
- 客户端需要的类型,服务端能力接收的东西
- 进行内容协商的最佳匹配媒体类型
- 用支持将对象转为最佳匹配媒体类型的converter,调用它进行转化
导入了jackson处理的xml的包,xml的converter就会自动进来
//WebMvcConfigurationSupport jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); if (jackson2XmlPresent) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build())); }
5.自定义MessageConverter
实现多协议数据兼容。json、xml、x-xw
0.@ResponseBody响应出去的数据调用RequestResponseBodyMethodProcessor处理
1.Processor处理方法返回值。通过MessageConverter处理
2.所有MessageConverter合起来可以支持各种媒体类型数据的操作(读,写)
3.内容协商找到最终的MessageConverter
修改SpringMVC的什么功能,只有一个入口,给容器中添加一个WebMvcConfigurer
@Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { } }; }
编写自定义MessageConverter
/** * 自定义的converter */ public class XwMessageConverter implements HttpMessageConverter<Pepole> { @Override public boolean canRead(Class<?> clazz, MediaType mediaType) { return false; } @Override public boolean canWrite(Class<?> clazz, MediaType mediaType) { return clazz.isAssignableFrom(Pepole.class); } //服务器要统计所有MessageConverter都能写出哪些内容类型 //application/x-xw @Override public List<MediaType> getSupportedMediaTypes() { return MediaType.parseMediaTypes("application/x-xw"); } @Override public Pepole read(Class<? extends Pepole> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } @Override public void write(Pepole pepole, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { //自定义协议数据的写出 String data = pepole.getUserName()+";"+pepole.getAge()+";"+pepole.getBirth(); //写出去 OutputStream body = outputMessage.getBody(); body.write(data.getBytes()); } }
把自定义converter添加进去
@Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new XwMessageConverter()); }
编写自定义协商策略
@Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { //Map<String, MediaType> mediaTypes Map<String, MediaType > mediaTypes = new HashMap<>(); //指定支持解析哪些参数对应的哪些媒体类型 mediaTypes.put("json",MediaType.APPLICATION_JSON); mediaTypes.put("xml",MediaType.APPLICATION_XML); mediaTypes.put("x-xw",MediaType.parseMediaType("application/x-xw")); //基于参数(自定义则覆盖了默认的) ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes); //基于请求头 HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy(); configurer.strategies(Arrays.asList(strategy,headerContentNegotiationStrategy)); }
debug发现协商策略变成3种了
浏览器请求:http://localhost:8080/test/pepole?format=x-xw
有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效
11.视图解析与模板引擎
1.视图解析
视图解析:SpringBoot默认不支持jsp,需要引入第三方模板引擎技术实现页面渲染
1.视图解析原理流程
1.目标方法处理的过程中,都会被放到ModelAndViewContainer里面,包括数据和视图地址
2.方法的参数是一个自定义类型对象(从参数请求中确定),把他重新放到ModelAndViewContainer
3.任何目标方法执行完成以后都会返回ModelAndView(数据和视图地址)
4.processDispatchResult处理派发结果(页面该如何响应)
- 1.render(mv, request, response);进行页面渲染逻辑
- 1.根据方法的String返回值得到View对象 【定义了页面的渲染逻辑】
- 1.所有的视图解析器尝试是否能根据当前返回值得到View对象
- 2.得到了 redirect:main -->Thymeleaf new RedirectView()
- 3.ContentNegotiatingViewResolver包含了下面所有视图解析器,内部还是利用下面所有的视图解析器得到视图对象
- 4.view.render(mv.getModelInternal(), request, response);视图对象调用自定义的render进行页面渲染工作
- RedirectView如何渲染【重定向到一个页面】
- 1.获取目标url地址
- 2.response.sendRedirect(encodedURL);
- 1.根据方法的String返回值得到View对象 【定义了页面的渲染逻辑】
- 视图解析:
- 返回值以**forward:**开始:new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forword(request,response);
- 返回值以**redirect:**开始:new RedirectView() --> render就是重定向
- 返回值是普通字符串:new ThymeleafView()
2.拦截器
1.编写拦截器
//登入检查 //1.配置好拦截器要拦截哪些请求 //2.把这些配置放在容器中 public class LoginInterceptor implements HandlerInterceptor { @Override //目标方法执行前 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //登入检查逻辑 HttpSession session = request.getSession(); Object loginUser = session.getAttribute("loginUser"); if(loginUser != null){ //放行 return true; } request.setAttribute("msg","请先登入"); //拦截住,未登入,跳转到登入页 //response.sendRedirect("/"); request.getRequestDispatcher("/").forward(request,response); return false; } @Override //目标方法执行完成以后 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override //页面渲染后 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
2.拦截器注册到容器中
@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
4.文件上传
1.表单
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data"> <div class="form-group"> <label for="exampleInputEmail1">邮箱</label> <input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email"> </div> <div class="form-group"> <label for="exampleInputPassword1">名字</label> <input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password"> </div> <div class="form-group"> <label for="exampleInputFile">头像</label> <input type="file" name="headerImg" id="exampleInputFile"> </div> <div class="form-group"> <label for="exampleInputFile">生活照</label> <input type="file" name="photos" multiple> </div> <div class="checkbox"> <label> <input type="checkbox"> Check me out </label> </div> <button type="submit" class="btn btn-primary">提交</button> </form>
2.controller
//MultipartFile 自动封装表单上传来的文件 @PostMapping("/upload") public String upload(@RequestParam("email") String email, @RequestParam("username") String username, @RequestPart("headerImg") MultipartFile headerImg, @RequestPart("photos") MultipartFile[] photos) throws IOException { System.out.println("邮箱:"+email+"\t名字"+username+"\t文件名"+headerImg.getName()+"\t文件数量"+photos.length); if(!headerImg.isEmpty()){ //保存到文件服务器,oss服务器 String originalFilename = headerImg.getOriginalFilename(); headerImg.transferTo(new File("E:\\idea程序\\Maven\\springboot-study\\springboot-02\\src\\main\\java\\com\\wang\\file\\"+originalFilename)); } if(photos.length>0){ for (MultipartFile photo : photos) { if(!photo.isEmpty()){ String originalFilename = photo.getOriginalFilename(); photo.transferTo(new File("E:\\idea程序\\Maven\\springboot-study\\springboot-02\\src\\main\\java\\com\\wang\\file\\"+originalFilename)); } } } return "main"; }
3.修改文件上传大小限制
#单个文件上传最大限制 spring.servlet.multipart.max-file-size=10MB #所有的文件总大小 spring.servlet.multipart.max-request-size=100MB
4.自动配置原理
文件上传自动配置类MultipartAutoConfiguration
--MultipartProperties
- 自动配置好了StandardServletMultipartResolver【文件上传解析器】
- 原理步骤
- 1.请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
- 2.参数解析器来解析请求中的文件内容封装成MultipartFile
- 3.将request中文件信息封装为一个Map;MultiValueMap<String,MultipartFile>
FileCopyUtils,实现文件流的拷贝
2.模板引擎
1.thymeleaf简介
现代化、服务端Java模板引擎
2.基本语法
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | ${...} | 获取请求域、session域、对象等值 |
选择变量 | *{...} | 获取上下文对象值 |
消息 | #{...} | 获取国际化等值 |
链接 | @{...} | 生成链接 |
片段表达式 | ~{...} | jsp:include 作用,引入公共页面片段 |
3.thymeleaf使用
1.导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
2.自动配置好了thymeleaf
@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(ThymeleafProperties.class) @ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class }) @AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class }) public class ThymeleafAutoConfiguration {}
自动配置好的策略
- 1.所有的thymeleaf的配置值都在
ThymeleafProperties
中 - 2.配置好了
SpringTemplateEngine
- 3.配置好了
ThymeleafViewResolver
- 4.我们只需要直接开发页面
public static final String DEFAULT_PREFIX = "classpath:/templates/"; //前缀 public static final String DEFAULT_SUFFIX = ".html"; //后缀
3.页面开发
success.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1 th:text="${msg}">我是小王</h1> <span> <a href="www.bibi.com" th:href="${link}">去b站</a> </span> </body> </html>
controller
@RequestMapping("/xw") public String xw(Model model){ //model中的数据会被放在请求域中 model.addAttribute("msg","你好,thymeleaf"); model.addAttribute("link","http://www.baidu.com"); return "success"; }
12.异常处理
1、错误处理
1、默认规则
- 默认情况下,Spring Boot提供
/error
处理所有错误的映射 - 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
- 要对其进行自定义,添加
View
解析为error
- 要完全替换默认行为,可以实现
ErrorController
并注册该类型的Bean定义,或添加ErrorAttributes类型的组件
以使用现有机制但替换其内容。
2、定制错误处理逻辑
-
自定义错误页
-
@ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
//处理整个web controller 异常 @Slf4j @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler({ArithmeticException.class,NullPointerException.class}) //处理异常 public String handleArithException(Exception e){ log.error("异常是:{ }",e); return "login"; //视图地址 } }
-
@ResponseStatus+自定义异常
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户数量过多") public class UserTooManyException extends RuntimeException{ public UserTooManyException() { } public UserTooManyException(String message) { super(message); } }
3.异常处理自动配置原理
- ErrorMvcAutoConfiguration 自动配置异常处理规则
- 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
- public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered
- DefaultErrorAttributes 定义错误页面中可以包含哪些数据
- 容器中的组件:类型:BasicErrorController -> id:basicErrorController
- 处理路径/error路径的请求;页面响应 new ModelAndView("error", model)
- 容器中有组件:View-->id是error(响应默认错误页)
- 容器中放组件:BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象
- 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
- 如果发生错误,会以Http的状态码作为视图页地址(viewName),找到真正的页面
- error/4xx,5xx.html
- 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
如果想要返回页面,就会找error视图【StaticView】(默认是个白页)
写json
错误页
4.异常处理步骤流程
1.执行目标方法,目标方法运行期间有任何异常都会被catch,而且会标志当前请求结束;并且用dispatchException
2.进入视图解析流程(页面渲染?)processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
3.mv = processHandlerException处理handler发生的异常,处理完成返回ModelAndView
-
1.遍历所有的handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器】
-
-
2.系统默认的异常解析器
-
- 1.DefaultErrorAttributes先来处理异常,把异常信息保存在request域中,并且返回null
- 2.默认没有任何人能处理异常,所以异常会被抛出
- 1.如果没有人能处理,最终底层会发送/error请求,会被底层的BasicErrorController处理
- 2.解析错误视图,遍历所有的ErrorViewResolver看谁能解析
- 3.默认的DefaultErrorViewResolver,作用是把响应状态码作为错误页的地址,拼接成error/500.html
- 4.模板引擎最终响应这个页面error/500.html
3.数据访问
1.引入druid-starter
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency>
2.分析自动配置
- 扩展配置项 spring.datasource.druid
- DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
- DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
- DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
- DruidFilterConfiguration.class}) 所有Druid自己filter的配置
3.配置示例
spring: datasource: url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8 username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver druid: filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙) stat-view-servlet: # 配置监控页功能 enabled: true login-username: admin login-password: 123456 resetEnable: false web-stat-filter: # 监控web enabled: true urlPattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' aop-patterns: com.wang.* #监控SpringBean
2.整合mybatis
1.导入依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency>
3.整合mybatis-plus
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency>