2022年12月19日 09点36分 正式开始
SpringBoot 基础入门 SpringBoot简介 SpringBoot可以整合Spring家族的其他框架  记住这个应该就行了
为什么要使用SpringBoot? 能快速创建出生产级别的Spring应用
 
SpringBoot的优点
 
创建独立Spring应用 
内嵌web服务器 
自动starter依赖,简化构建配置 
自动配置Spring以及第三方功能 
提供生产级别的监控,健康检查及外部化配置 
无代码生成,无需编写XML 
 
starter:这个是真的牛逼啊,之前写一个模块就要导一堆的jar包(pom里面),现在要是写一个web应用,只需要导入一个web的jar包就可以了 
还有自动配置Spring以及第三方功能,现在就成了:项目创建好,直接开发业务逻辑,不用写太多的配置文件,各种整合的内容
SpringBoot是整合Spring技术栈的一站式框架 SpringBoot是简化Spring技术栈的快速开发脚手架
 
SpringBoot的缺点
人称版本帝,迭代快,需要时刻关注变化 
封装太深,内部原理复杂,不容易精通 
 
时代背景 微服务 
微服务是一种架构风格 
每个服务运行在自己的进程内,也就是可独立部署和升级 
服务之间使用轻量级HTTP交互 
服务围绕业务功能拆分 
可以由全自动部署机制独立部署 
去中心化,服务自治,服务可以使用不同的语言,不同的存储技术 
 
分布式  分布式的困难:
远程调用 
服务发现 
负载均衡 
服务容错 
配置管理 
服务监控 
链路追踪 
日志管理 
 
分布式的解决SpringBoot+SpringCloud 
这SpringBoot现在已经更新到3了,老师讲的是2,刚才百度了一下 发现3的Java版本已经更新到17了,这谁敢用
SpringBoot入门 先说前提啊:老师用的是2.3.4.RELEASE 但是现在阿里云的镜像里已经没有这个jar包了,所以我用的是2.4.13
入门案例 需求:浏览器发哦是那个/hello请求,响应Hello,SpringBoot2
卧槽,太强了吧,这也太强了吧
下面看步骤:
导入依赖 
 
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 <?xml version="1.0"  encoding="UTF-8" ?> <project  xmlns ="http://maven.apache.org/POM/4.0.0"           xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"           xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >     <modelVersion > 4.0.0</modelVersion >      <groupId > com.zzmr</groupId >      <artifactId > boot-01-helloworld</artifactId >      <version > 1.0-SNAPSHOT</version >      <properties >          <maven.compiler.source > 8</maven.compiler.source >          <maven.compiler.target > 8</maven.compiler.target >          <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding >      </properties >      <parent >          <groupId > org.springframework.boot</groupId >          <artifactId > spring-boot-starter-parent</artifactId >          <version > 2.4.13</version >      </parent >      <dependencies >          <dependency >              <groupId > org.springframework.boot</groupId >              <artifactId > spring-boot-starter-web</artifactId >          </dependency >      </dependencies >  </project > 
 
先写一个主类,这个类负责运行项目 要加上@SpringBootApplication注解 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package  com.zzmr.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);     } } 
 
写处理请求的类 这里要注意使用的是@RestController注解,是给浏览器写回一个数据,而不是跳转页面 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package  com.zzmr.boot.controller;import  org.springframework.stereotype.Controller;import  org.springframework.web.bind.annotation.GetMapping;import  org.springframework.web.bind.annotation.RequestMapping;import  org.springframework.web.bind.annotation.ResponseBody;import  org.springframework.web.bind.annotation.RestController;@RestController public  class  HelloController  {    @RequestMapping("/hello")      public  String handle01 ()  {         return  "Hello, SpringBoot 2!" ;     } } 
 
然后,牛逼的地方来了,直接运行主方法 然后在浏览器访问http://localhost:8080/hello直接得到 :
这相当于省略了什么?省略了配置web.xml,省略了配置SpringMVC的配置文件,省略了配置tomcat,甚至连页面都没写 牛逼
application.properties  这个是用来干什么的?它可以用来配置很多东西 甚至tomcat的端口号都可以用它来配置 server.port=8888 此时,端口号就改成8888了
 它能修改哪些内容?点我查看 
还有一个打包的插件
1 2 3 4 5 6 7 8 9 <build >     <plugins >          <plugin >              <groupId > org.springframework.boot</groupId >              <artifactId > spring-boot-maven-plugin</artifactId >              <version > ${project.parent.version}</version >          </plugin >      </plugins >  </build > 
 
甚至可以将web工程打包成可执行的jar包,然后在cmd里执行java -jar boot-01-helloworld-1.0-SNAPSHOT.jar,就可以在浏览器直接访问 
真就,依赖一导,其他几乎都不用操心了
注意:
了解自动配置原理 SpringBoot特点 1.1 依赖管理 
父项目做依赖管理
什么意思呢,就是这个spring-boot-starter-parent,就是父项目,然后它管理了后面用到的依赖的版本
 
 
 
1 2 3 4 5 <parent >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-parent</artifactId >      <version > 2.4.13</version >  </parent > 
 
就直接可以在后面添加依赖的时候,不用写版本号
 
1 2 3 4 5 6 <dependencies >     <dependency >          <groupId > org.springframework.boot</groupId >          <artifactId > spring-boot-starter-web</artifactId >      </dependency >  </dependencies > 
 
这个项目的父项目:
 
1 2 3 4 5 <parent >   <groupId > org.springframework.boot</groupId >    <artifactId > spring-boot-dependencies</artifactId >    <version > 2.4.13</version >  </parent > 
 
再点进去,就会发现这个项目的properties标签中有很多很多的版本信息,几乎声明了所有开发中常用的jar的版本号(也叫自动版本仲裁机制)
 
开发导入starter场景启动器
什么是starter呢? 在以后的开发中,会遇到很多的spring-boot-starter-,*表示某种场景 那么只要引入starter,那这个场景下常规所需要的依赖全部会自动导入 比如这个spring-boot-starter-web,点进去就会发现,它引入了大部分web所需要的依赖,这里其实是使用了*maven的特性:依赖的传递性   那*有哪些呢,点击查看 
 
 
无需关注版本号,自动版本仲裁
引入依赖默认都可以不写版本 引入非版本仲裁的jar,要写版本号(就是dependencies中没有的依赖)
 
 
可以修改版本号
修改版本号只需要properties标签中添加上某个依赖的版本号即可 比如要更改mysql的版本为5.1.43 此时在properties标签中添加mysql.version,然后刷新,即可完成修改 **如何知道要修改的key呢?要去spring-boot-dependencies中搜索
 
 
 
1 <mysql.version > 5.1.43</mysql.version > 
 
1.2 自动配置 
1 2 3 4 5 6 <dependency >   <groupId > org.springframework.boot</groupId >    <artifactId > spring-boot-starter-tomcat</artifactId >    <version > 2.4.13</version >    <scope > compile</scope >  </dependency > 
 
自动配好SpringMVC
引入SpringMVC全套组件 
自动配好了SpringMVC常用组件(功能) 
 
 
自动配好Web常用功能,如字符编码问题
SpringBoot帮我们配置好了所有web开发的常见场景 
 
 
默认的包结构
之前要指定controller包啊什么包什么包在什么地方,进行扫描 
而现在,主程序所在包以及下面的所有子包里面的组件都会被默认扫描进来  
 
 
要注意啊,只有放在主程序所在包以及子包里面才能被默认扫描出来,但是如果想自定义扫描的位置呢?,就要在@SpringBootApplication注解中添加scanBasePackages属性了:@SpringBootApplication(scanBasePackages = “com.zzmr”) 
当然还有另一个注解:@ComponentScan(),这个注解就是专门用于扫描包的,叫做包扫,这个以后学,现在不能直接用 
而一个@SpringBootApplication就相当于 
 
1 2 3 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan("包") 
 
各种配置拥有默认值
默认配置都是映射到MultipartProperties 
配置文件的值最终被绑定到每个类上,这个类会在容器中创建对象 
 
 
按需加载所有自动配置项
非常多的starter 
引入了哪些场景,这个场景的自动配置就会开启 
SpringBoot所有的自动配置功能都在spring-boot-autoconfigure包里面 
 
 
…..
 
 
容器功能 组件添加 1. @Configuration 之前都是在Spring的配置文件中设置bean标签,在bean标签中设置组件 而现在是创建配置类
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.zzmr.boot.config;import  com.zzmr.boot.bean.Pet;import  com.zzmr.boot.bean.User;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;@Configuration(proxyBeanMethods = false)  public  class  MyConfig  {         @Bean       public  User user01 () {         return  new  User ("zhangsan" ,18 );     }     @Bean("tom")      public  Pet tomcatPet () {         return  new  Pet ("tomcat" );     } } 
 
这时可以获取ioc容器,然后打印一些容器里的组件名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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);     } } 
 
即可得到这两个组件,要注意的是,SpringApplication.run(MainApplication.class, args)的返回值就是IOC容器
而且,配置类里面使用@Bean注解注册的组件,默认也是单例的
1 2 3 4 5     Pet  tom01  =  run.getBean("tom" , Pet.class);     Pet  tom02  =  run.getBean("tom" , Pet.class);          System.out.println("组件" +(tom01==tom02)); 
 
会输出true
而且配置类也是容器中的一个组件 
Full模式与Lite模式 
配置类组件之间无依赖关系用Lite模式(proxyBeanMethods = false),加速容器启动过程,减少判断 
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(proxyBeanMethods = true) 
 
2. @Import @Import({User.class, DBHelper.class}) 给容器中自动创建出这两个类型的组件,默认组建的名字就是全类名
3. @Conditional 条件装配:满足Conditional指定的条件,则进行组件注入
这个注解有很多子注解?是这样叫吗
比如    @ConditionalOnBean(name = “tom”) 先把tom的Bean给注释掉,让ioc容器中不存在tom组件
1 2 3 4 5 6 7 8 9 10 @ConditionalOnBean(name = "tom") @Bean  public  User user01 () {    return  new  User ("zhangsan" ,18 ); } public  Pet tomcatPet () {    return  new  Pet ("tomcat" ); } 
 
这时,如果容器中没有名称为tom的组件,则user01也不会创建
1 2 3 boolean  tom  =  run.containsBean("tom" );System.out.println("容器中tom组件 "  + tom); System.out.println("user01:" +run.containsBean("user01" )); 
 
加上@ConditionalOnBean(name = “tom”)之后,两个输出的都是false
原生配置文件引入 1. @ImportResource 
什么意思呢,就比如之前用的都是xml文件配置的bean 现在想在SpringBoot中继续使用,是不能直接拿来使用的 可以使用@ImportResource注解 @ImportResource(“classpath:bean.xml”) 这样就可以导入原来配置文件中的bean了
配置绑定 如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用
说个题外话,这个tester68,我还以为没有ins键呢,原来fn+del就是了,所以我现在想用快捷键来生成一些东西,就可以用alt+fn+del
 
方式一 
可以用一个类来接收properties的值 比如properties中的是:
1 2 3 4 5 6 server.port =8888 spring.servlet.multipart.max-file-size =10MB mycar.band =BTD mycar.price =100000 
 
此时,可以用一个类:
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 package  com.zzmr.boot.bean;import  org.springframework.boot.context.properties.ConfigurationProperties;import  org.springframework.stereotype.Component;@Component @ConfigurationProperties(prefix = "mycar") public  class  Car  {    private  String band;     private  Integer price;     public  Car (String band, Integer price)  {         this .band = band;         this .price = price;     }     public  Car ()  {     }     @Override      public  String toString ()  {         return  "Car{"  +                 "band='"  + band + '\''  +                 ", price="  + price +                 '}' ;     }     public  String getBand ()  {         return  band;     }     public  void  setBand (String band)  {         this .band = band;     }     public  Integer getPrice ()  {         return  price;     }     public  void  setPrice (Integer price)  {         this .price = price;     } } 
 
加上@Component @ConfigurationProperties(prefix = “mycar”) 这两个注解,prefix的值就是properties文件中要识别的数据的前缀 比如上面是mycar.band=BYD,那根据这个注解,就可以找到这条数据,并将点后面的属性跟类中的属性进行比较,如果相等,则赋值
1 2 3 4 5 6 7 @Autowired Car car; @RequestMapping("/car") public  Car car () {    return  car; } 
 
此时就能在前端页面得到数据
还有第二种方式
方式二 
需要在配置类中加上@EnableConfigurationProperties注解 作用:
开启Car配置绑定功能 
把Car这个组件自动注册到容器中 和第一种方法的区别就是不用写@Component注解了 
 
自动配置原理入门 引导加载自动配置类 在主程序类上添加的@SpringBootApplication注解,相当于
1 2 3 4 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), 		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) 
 
的组合
@SpringBootConfiguration 代表当前是个配置类 
@ComponentScan 指定扫描哪些,Spring注解, 
@EnableAutoConfiguration 
 
1 2 3 @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public  @interface  EnableAutoConfiguration {}
 
@AutoConfigurationPackage,自动配置包
 
1 2 3 4 @Import(AutoConfigurationPackages.Registrar.class)   public  @interface  AutoConfigurationPackage {}
 
@Import(AutoConfigurationImportSelector.class)
 
1 2 3 4 5 6 7 getAutoConfigurationEntry(annotationMetadata); 1.  利用这个方法,给容器中批量导入一些组件2.  调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);获取所有需要导入容器中的配置类3.  利用工厂加载 Map<String, List<String>> loadSpringFactories (ClassLoader classLoader)  {} 得到所有的组件4.  从 META-INF/spring.factories`位置来加载一个文件    默认扫描我们当前系统里面所有 `META-INF/spring.factories`位置的文件     spring-boot-autoconfigure-2.3 .4 .RELEASE.jar`包里面也有 `META-INF/spring.factories` 
 
1 2 文件里面写死了,SpringBoot一启动,就要给容器中加载的所有配置类 虽然这些场景的所有自动配置启动的时候默认全部加载,但是xxxxAutoConfiguration按照条件装配规则(@Conditional),最终会按需配置 
 
自动配置流程 以 DispatcherServletAutoConfiguration的内部类 DispatcherServletConfiguration为例子:
1 2 3 4 5 6 7 8 9 @Bean @ConditionalOnBean(MultipartResolver.class)   @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)  public  MultipartResolver multipartResolver (MultipartResolver resolver)  {	 	 	 	return  resolver; } 
 
SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先 。
总结 :
SpringBoot先加载所有的自动配置类  xxxxxAutoConfiguration 
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxxProperties里面读取,xxxProperties和配置文件进行了绑定) 
生效的配置类就会给容器中装配很多组件 
只要容器中有这些组件,相当于这些功能就有了 
定制化配置
用户直接自己@Bean替换底层的组件 
用户去看这个组件是获取的配置文件什么值就去修改。 
 
 
 
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值  —-> application.properties 
最佳实践 
引入场景依赖
 
查看自动配置了哪些(选做)
自己分析,引入场景对应的自动配置一般都生效 
在配置文件中写上:debug=true,此时运行项目,控制台会打印用到的配置类和没用到的配置类,Negative matches都是不生效的,Postive matches都是生效的配置 
 
 
是否需要修改
参照文档修改配置项
 
自定义加入或者替换组件
 
自定义@Customizer 
….. 
 
 
 
开发小技巧 Lombok 简化JavaBean开发,之前浩南给我说这个了,当时是不会弄,没想到这里竟然会教
使用步骤:
引入依赖: 
 
1 2 3 4 <dependency >     <groupId > org.projectlombok</groupId >      <artifactId > lombok</artifactId >  </dependency > 
 
搜索安装lombok插件 
 
使用 
 
1 2 3 4 5 6 7 8 9 10 11 12 @Data  @ToString @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode public  class  Lom  {    private  String name;     private  Integer age; } 
 
使用以上的注解标识到bean类上就行了
还有一个重要的使用是@Slf4j,然后可以在方法中直接log.info(“请求进来了….”);将内容写入到日志文件中 
依赖:
1 2 3 4 5 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-devtools</artifactId >      <optional > true</optional >  </dependency > 
 
配置完,按ctrl+F9,可以实现重新编译项目 不过我说实话现阶段用处不大,而且这个好像很吃内存…
Spring Initailizr 项目初始化向导,这个玩意牛逼啊
在新建项目的地方选择Spring Initailizr,然后输入一些内容,项目就自动创建好了,里面的配置基本都是写好的
非常非常全:
 不过要注意SpringBoot的版本,这个里面默认最低只有2.7了,不过可以创建好项目之后去pom里面更改
核心功能 配置文件-yaml 上次接触这个配置文件还是在博客里面,Hexo的大部分配置文件都是yaml
基础语法 
key: value; kv之间有空格 
大小写敏感 
使用缩进表示层级关系 
缩进不允许使用tab,只允许空格 
缩进的空格数不重要,只要相同层级的元素左对齐即可 
‘#’表示注释 
“与””表示字符串内容,会被转义/不转义 
 
示例:
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 person:   userName:  zhangsan    boss:  true    birth:  2019 /12/9    age:  18       interests:      -  篮球      -  足球    animal:  [ 阿猫,阿狗  ]            score:  { english:  80 ,math:  90  }   salary:      -  9999.99      -  9999.98    pet:      name:  阿狗      weight:  99.99    allPets:      sick:        -  { name:  阿狗 ,weight:  99.99  }       -  name:  阿毛          weight:  88.88        -  name:  阿虫          weight:  77.77      health:        -  { name:  阿yi ,weight:  99.99  }       -  { name:  阿er ,weight:  99.99  } 
 
两个bean类
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 Person.java package  com.zzmr.boot01helloworld2.bean;import  lombok.AllArgsConstructor;import  lombok.Data;import  lombok.NoArgsConstructor;import  org.springframework.boot.context.properties.ConfigurationProperties;import  org.springframework.stereotype.Component;import  java.util.Date;import  java.util.List;import  java.util.Map;import  java.util.Set;@ConfigurationProperties(prefix = "person") @Component @Data @AllArgsConstructor @NoArgsConstructor 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> salary;     private  Map<String, List<Pet>> allPets; } Pet.java package  com.zzmr.boot01helloworld2.bean;import  lombok.AllArgsConstructor;import  lombok.Data;import  lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor 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 package  com.zzmr.boot01helloworld2.controller;import  com.zzmr.boot01helloworld2.bean.Person;import  org.springframework.beans.factory.annotation.Autowired;import  org.springframework.web.bind.annotation.RequestMapping;import  org.springframework.web.bind.annotation.RestController;@RestController public  class  HelloController  {    @Autowired      Person person;     @RequestMapping("/person")      public  Person person () {         return  person;     } } 
 
此时访问进行访问
以后都是推荐使用yaml进行配置了
要注意的就是单引号和双引号的使用  单引号会将\n作为字符串输出,  双引号会将\n作为换行输出双引号不会转义,单引号会转义,转义是什么?是表示原来的字面意思  或者说单引号会输出普通字符串,而双引号会输出它原本的意思 
但是你有没有发现,直接在yml里面写自己配的bean的时候,是没有任何提示的,要想有提示怎么办? 加上这个依赖:
1 2 3 4 5 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-configuration-processor</artifactId >      <optional > true</optional >  </dependency > 
 
然后重启项目
但是要注意的一点是,默认打包时会把spring-boot-configuration-processor也打入到包中,但是这个依赖只是编写项目时要用的,项目在实际运行时是不需要的,所以可以用这个插件,在打包时不将spring-boot-configuration-processor打进包里,让其只在编码的时候有用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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 > 
 
但是我现在这个版本的SpringBoot(2.4.3),好像不配置也不会将spring-boot-configuration-processor打包进去,当才去看没找到 好像2.4.2就解决了
Web开发 进入正题?点击查看Web开发官方文档 
简单功能分析 静态资源访问 还是先创建项目,咱们试试直接用2.7.6?也就是默认最低的哪个版本? 试试吧,可能会向下兼容 静态资源官方文档Static Content 
静态资源目录:  类路径下:called /static (or /public or /resources or /META-INF/resources) 此时,创建这些路径,然后在里面放上静态资源(图片)作为测试
 此时直接在浏览器能访问到的(根路径/+静态资源名)
原理: 静态映射/** ,请求进来,先去找Controller,看能不能处理,如果能处理,就由Controller处理,如果不能处理,又会交给静态资源处理,静态资源就回去上面那几个目录寻找资源,如果静态资源也找不到,就会报404
静态资源访问前缀 以后的开发对于访问静态资源都会用到访问前缀 需要在yml中配置
1 2 3 spring:   mvc:      static-path-pattern:  /res/**  
 
当前项目+static-path-pattern+静态资源名=回去静态资源文件夹下找
静态资源的默认路径也是可以改的  但是我感觉没必要,至少现在没必要
欢迎页支持和favicon网页图标 
静态资源路径下 index.html
可以配置静态资源路径 
但是不可以配置静态资源的访问前缀,否则导致index.html不能被默认访问 直接将index.html放入static下面,然后浏览器访问localhost:8080/就可以访问到index.html 
 
 
 
favicon网页图标  直接将一个叫favicon.ico的图片放到static目录下即可 这功能用处不大吧…
静态资源配置原理 
SpringBoot启动默认加载xxxAutoConfiguration类(自动配置类) 
SpringMVC功能的自动配置类 WebMvcAutoConfiguration生效 
 
1 2 3 4 5 6 7 @AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, 		ValidationAutoConfiguration.class }) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) public  class  WebMvcAutoConfiguration  {}
 
1 2 3 4 5 @Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class }) @Order(0) public  static  class  WebMvcAutoConfigurationAdapter  implements  WebMvcConfigurer , ServletContextAware {}
 
配置文件的相关属性和xxx进行了绑定,WebMvcProperties==spring.mvc,WebProperties==spring.web(这里又和老师讲的不太一样了,这里的WebProperties代替了之前的ResrouceProperties,spring.web也代替了spring.resources) 
 
配置类只有一个有参构造器
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15        public  WebMvcAutoConfigurationAdapter (WebProperties webProperties, WebMvcProperties mvcProperties, 		ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, 		ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, 		ObjectProvider<DispatcherServletPath> dispatcherServletPath, 		ObjectProvider<ServletRegistrationBean<?>> servletRegistrations)  {	this .resourceProperties = webProperties.getResources(); 	this .mvcProperties = mvcProperties; 	this .beanFactory = beanFactory; 	this .messageConvertersProvider = messageConvertersProvider; 	this .resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); 	this .dispatcherServletPath = dispatcherServletPath; 	this .servletRegistrations = servletRegistrations; 	this .mvcProperties.checkConfiguration(); } 
 
资源处理的默认规则 
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 ... public  class  WebMvcAutoConfiguration  {    ... 	public  static  class  EnableWebMvcConfiguration  extends  DelegatingWebMvcConfiguration  implements  ResourceLoaderAware  {         ... 		@Override  		protected  void  addResourceHandlers (ResourceHandlerRegistry registry)  { 			super .addResourceHandlers(registry); 			if  (!this .resourceProperties.isAddMappings()) { 				logger.debug("Default resource handling disabled" ); 				return ; 			} 			ServletContext  servletContext  =  getServletContext(); 			addResourceHandler(registry, "/webjars/**" , "classpath:/META-INF/resources/webjars/" ); 			addResourceHandler(registry, this .mvcProperties.getStaticPathPattern(), (registration) -> { 				registration.addResourceLocations(this .resourceProperties.getStaticLocations()); 				if  (servletContext != null ) { 					registration.addResourceLocations(new  ServletContextResource (servletContext, SERVLET_LOCATION)); 				} 			}); 		}         ...        }     ... } 
 
可以配置禁止所有静态资源规则
1 2 3 4 spring:   web:      resources:        add-mappings:  false  
 
新版的要在web下,老版的还是
1 2 3 spring:   resources:        add-mappings:  false     
 
请求参数处理 请求映射 
@xxxMapping 
Rest风格支持(使用Http请求方式动词来表示对资源的操作,这个在mvc里已经学过了,就是查询GET,修改PUT,删除DELETE,添加POST,然后请求的路径都是/user) 
 
但是需要手动开启
1 2 3 4 5 spring:   mvc:      hiddenmethod:        filter:          enabled:  true  
 
简单测试 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <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  type ="hidden"  name ="_method"  value ="PUT" >      <input  value ="REST-PUT"  type ="submit" >  </form > <form  action ="/user"  method ="post" >     <input  type ="hidden"  name ="_method"  value ="DELETE" >      <input  value ="REST-DELETE"  type ="submit" >  </form > 
 
控制器方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23  @GetMapping("/user")      public  String getUser () {         return  "GET-张三" ;     }     @PostMapping("/user")      public  String saveUser () {         return  "POST-张三" ;     }     @PutMapping("/user")      public  String putUser () {         return  "PUT-张三" ;     }     @DeleteMapping("/user")      public  String deleteUser () {         return  "DELETE-张三" ;     } 
 
此时就可以使用REST风格了 那原理又是什么?
表单提交会带上_method=PUT(DELETE) 
请求过来被HiddenHttpMethodFilter拦截
请求是否正常,是否为post请求
获取到_method的值(其中是PUT或者是DELETE) 
兼容PUT,DELETE,和PATCH(这个没学过) 
原生request(post),一个包装模式requestWrapper重写了getMethod方法,返回的是传入的值 
过滤器链放行的时候用wrapper,以后的方法调用getMethod是调用的requestWrapper的 其实就是过滤POST请求,然后判断是哪个请求,然后返回REST使用客户端工具  比如使用POSTMAN直接发送PUT请求,这时就不会走上面的流程(无需Filter ),而是直接就发送过来PUT或者DELETE了 话说我还没用过POSTMAN,下一个看看吧 下了,也注册了,用的google邮箱注册的 
 
 
 
 
 
改变默认的_method 说实话为什么改这个?
 
不过要改的话也很简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package  com.zzmr.boot.config;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;import  org.springframework.web.filter.HiddenHttpMethodFilter;@Configuration(proxyBeanMethods = false) public  class  WebConfig  {    @Bean      public  HiddenHttpMethodFilter hiddenHttpMethodFilter () {         HiddenHttpMethodFilter  hiddenHttpMethodFilter  =  new  HiddenHttpMethodFilter ();         hiddenHttpMethodFilter.setMethodParam("_m" );         return  hiddenHttpMethodFilter;     } } 
 
创建这个配置类就行了,然后在setMethodParam()里面写上自定义的名字
请求映射原理 
 SpringMVC功能分析都从org.springframework.web.servlet.DispatcherServlet->doDispatch()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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); ... 
 
进入getHandler方法,会先获取所有的HandlerMapping(处理器映射)
 一共五个 RequestMappingHandlerMapping: 保存了所有@RequestMapping和handler的映射规则(项目一启动,就会扫描controller然后保存控制器方法) 看,在执行增强for循环时,第一个就是RequestMappingHandlerMapping,然后打开mapping
就会发现所有的控制器方法都在里面  一步步进入
 先根据url找,会找到四个
 找出最合适的
总结就是所有的请求映射都在HandlerMapping中
SpringBoot自动配置欢迎页的HandlerMapping,访问/能访问到index.html 
SpringBoot自动配置了默认的RequestMappingHandlerMapping 
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息
如果有就找到这个请求对应的handler 
如果没有就是下一个HandlerMapping 
 
 
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping,自定义HandlerMapping 
 
hahahha 看了好几集,一点笔记都没有写,一会一块写吧
各种注解 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 package  com.zzmr.boot.controller;import  org.springframework.web.bind.annotation.*;import  javax.servlet.http.Cookie;import  java.util.HashMap;import  java.util.List;import  java.util.Map;@RestController public  class  ParameterTestController  {    @GetMapping("/car/{id}/owner/{username}")      public  Map<String, Object> getCar (@PathVariable("id")  Integer id,                                        @PathVariable("username")  String name,                                       @PathVariable  Map<String, String> pv,                                       @RequestHeader("User-Agent")  String userAgent,                                       @RequestHeader  Map<String, String> header,                                       @RequestParam("age")  Integer age,                                       @RequestParam("inters")  List<String> inters,                                       @RequestParam  Map<String, String> params,                                       @CookieValue("Idea-6d070b92")  String cookie,                                       @CookieValue("Idea-6d070b92")  Cookie cookie1)  {        Map<String, Object> map = new  HashMap <>();         map.put("age" , age);         map.put("inters" , inters);         map.put("params" , params);         map.put("cookie" , cookie);         System.out.println(cookie1.getName() + "===="  + cookie1.getValue());         return  map;     }     @PostMapping("/save")      public  Map postMethod (@RequestBody  String content)  {         Map<String, Object> map = new  HashMap <>();         map.put("content" , content);         return  map;     }                         @GetMapping("/cars/sell")      public  Map carsSell (@MatrixVariable("low")  Integer low,                          @MatrixVariable("brand")  List<String> brand)  {        Map<String, Object> map = new  HashMap <>();         map.put("low" ,low);         map.put("brand" ,brand);         return  map;     } } 
 
看看就好,毕竟之前学mvc都已经讲过了 主要就是@PathVariable @RequestHeader @RequestParam这些注解不加参数,是会默认获取所有内容的,所以还是要注意以下 至于下面的矩阵变量,我这边连测试都不成功 就不尝试了
开启矩阵变量的两种方法
用配置类实现WebMvcConfigurer接口,然后重写configurePathMatch方法 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package  com.zzmr.boot.config;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;import  org.springframework.web.filter.HiddenHttpMethodFilter;import  org.springframework.web.servlet.config.annotation.PathMatchConfigurer;import  org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import  org.springframework.web.util.UrlPathHelper;@Configuration(proxyBeanMethods = false) public  class  WebConfig  implements  WebMvcConfigurer  {    @Override      public  void  configurePathMatch (PathMatchConfigurer configurer)  {         UrlPathHelper  urlPathHelper  =  new  UrlPathHelper ();                  urlPathHelper.setRemoveSemicolonContent(false );         configurer.setUrlPathHelper(urlPathHelper);     } } 
 
直接写一个WebMvcConfigurer方法 
 
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 package  com.zzmr.boot.config;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;import  org.springframework.web.filter.HiddenHttpMethodFilter;import  org.springframework.web.servlet.config.annotation.PathMatchConfigurer;import  org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import  org.springframework.web.util.UrlPathHelper;@Configuration(proxyBeanMethods = false) public  class  WebConfig  {    @Bean      public  WebMvcConfigurer webMvcConfigurer () {         return  new  WebMvcConfigurer () {             @Override              public  void  configurePathMatch (PathMatchConfigurer configurer)  {                 UrlPathHelper  urlPathHelper  =  new  UrlPathHelper ();                 urlPathHelper.setRemoveSemicolonContent(false );                 configurer.setUrlPathHelper(urlPathHelper);             }         };     } } 
 
请求处理-源码分析 没看错吧,32-42全是源码分析 这不把人看死了 怎么办,先看看评论吧 看吧,反正这个源码早晚都要看,不如先看一遍,然后等到后面需要加深理解,就再来看一遍
HandlerMapping中找到能处理请求的Handler(Controller.method) 
为当前Handler找一个适配器HandlerAdapter: RequestMappingHandlerAdapter 
 
HandlerAdapter 
 0. 支持方法上标注@RequestMapping
支持函数式编程 xxxxx 
 
执行目标方法 
1 2 3     mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 
 
怎么执行的呢
1 2 3 4 5 6 7 8 mav = invokeHandlerMethod(request, response, handlerMethod);                     Object  returnValue  =  invokeForRequest(webRequest, mavController,provideArgs);                              Object[] args = getMethodArgumentValues(request,mavContainer,providedArgs) 
 
参数解析器 -确定将要执行的目标方法的每一个参数的值是什么
SpringMVC目标方法能写多少种参数类型,取决于参数解析器
当前解析器是否支持解析这种参数 
支持就调用resolveArgument 
 
 这个可执行的方法,就是目标方法
如何确定目标方法每一个参数的值 
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 =======InvocableHandlerMethod.java====== 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; 	} 
 
挨个判断所有参数解析器哪个支持解析这个参数  听的是个鸡巴 听得一愣一愣的
不听了,直接跳吧,跳到42集,这32-42就等我看完后面的再重新看 
视图解析与模板引擎 Thymeleaf 又是Thymeleaf,这次能深入了解一下了吧 之前mvc都是看看怎么用 并没有系统的学这个东西,但是我感觉这个视频也不会深入讲这个东西,可能也只是教怎么用
命名空间: xmlns:th=”http://www.thymeleaf.org" 
先看看吧 首先引入依赖
1 2 3 4 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-thymeleaf</artifactId >  </dependency > 
 
thymeleaf已经自动配置好了
1 2 3 4 5 6 @AutoConfiguration(after = { WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class }) @EnableConfigurationProperties(ThymeleafProperties.class) @ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class }) @Import({ TemplateEngineConfigurations.ReactiveTemplateEngineConfiguration.class, 		TemplateEngineConfigurations.DefaultTemplateEngineConfiguration.class }) public  class  ThymeleafAutoConfiguration  {}
 
自动配好的策略
所有的thymeleaf的配置值都在ThymeleafProperties 
配置好了SpringTemplateEngine  
配好了ThymeleafViewResolver 
我们只需要直接开发页面 
 
1 2 3    public  static  final  String  DEFAULT_PREFIX  =  "classpath:/templates/" ; public  static  final  String  DEFAULT_SUFFIX  =  ".html" ;
 
使用上应该没什么难的,毕竟之前mvc都用了一些了 现在问题就是,源码那部分没看,会不会影响后面的学习? 看评论说好像没啥事 那就先继续看吧
Web实验-后台管理系统 离谱,SpringBoot版本现在最低已经变成2.7.7了
现在在干什么? 只是把那个前端的页面跟后端实现交互,写一些控制层啥的 没什么重要的
感觉这块笔记的话也没什么写的
要是想看直接去项目里面看吧
下面看原理
视图解析原理流程 
目标方法处理过程中,所有数据都会被放在ModelAndViewContainer里面,包括数据和视图地址 
方法的参数是一个自定义类型对象(从参数中确定的) ,把他重新放在ModelAndViewContainer中 
任何目标方法执行完成以后都会返回ModelAndView(数据和视图地址) 
processDispatchResult处理派发结果(决定页面如何响应)
render(mv,request,response);进行页面渲染
根据方法的String返回值得到View对象(定义了页面的渲染逻辑)
所有的视图解析器尝试是否能够根据当前返回值得到View对象 
得到了redirect:main.html—->Thymeleaf new RedirectView() 
ContentNegotiationViewResolver里面包含了下面所有的视图解析器,内部还是利用下面所有的视图解析器得到视图对象 
view.render(mv.getModelInternal(),request,response); 视图对象调用自定义的render进行页面渲染工作
RedirectView如何渲染(重定向到一个页面)
获取目标url地址 
response.sendRedirect(encodedURL); 
 
 
 
 
 
 
 
 
 
 
 
总结: 视图解析
返回值以forward开始: new InternalResourceView(forwardUrl);–>转发 request.getRequestDispatcher(path).forward(request,response); 
返回值以redirect开始: new RedirectView();—->render就是重定向 
返回值是普通字符串 new ThymeleafView()—> 
 
也可以自定义视图解析器+自定义视图
拦截器 Handlerinterceptor接口 这都是之前mvc里学过的内容了
看看代码吧
拦截器: 
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 package  com.zzmr.admin.interceptor;import  lombok.extern.slf4j.Slf4j;import  org.springframework.web.method.HandlerMethod;import  org.springframework.web.servlet.HandlerInterceptor;import  org.springframework.web.servlet.ModelAndView;import  javax.servlet.http.HttpServletRequest;import  javax.servlet.http.HttpServletResponse;import  javax.servlet.http.HttpSession;@Slf4j public  class  LoginInterceptor  implements  HandlerInterceptor  {         @Override      public  boolean  preHandle (HttpServletRequest request, HttpServletResponse response, Object handler)  throws  Exception {         String  requestURI  =  request.getRequestURI();         log.info("拦截的请求路径是{}" ,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 {         HandlerInterceptor.super .postHandle(request, response, handler, modelAndView);     }          @Override      public  void  afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)  throws  Exception {         HandlerInterceptor.super .afterCompletion(request, response, handler, ex);     } } 
 
将拦截器注册到容器中:
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 package  com.zzmr.admin.config;import  com.zzmr.admin.interceptor.LoginInterceptor;import  org.springframework.context.annotation.Configuration;import  org.springframework.web.servlet.config.annotation.InterceptorRegistry;import  org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration public  class  AdminWebConfig  implements  WebMvcConfigurer  {         @Override      public  void  addInterceptors (InterceptorRegistry registry)  {         registry.addInterceptor(new  LoginInterceptor ())                 .addPathPatterns("/**" )                                   .excludePathPatterns("/" , "/login" ,"/css/**" ,"/fonts/**" ,"/images/**" ,"/js/**" );     } } 
 
要注意静态资源的放行
拦截器原理 
根据当前请求,找到HandlerExecutionChain可以处理请求的handler以及handler的所有拦截器 
先来顺序执行 所有拦截器preHandle方法
如果当前拦截器preHandler返回为true,则执行下一个拦截器的preHandle 
如果当前拦截器返回为false,直接 倒叙执行所有已执行了preHandle()的拦截器的afterCompletion(); 
 
 
如果任何一个拦截器返回false,直接跳出不执行目标方法 
所有拦截器都返回True,执行目标方法 
倒叙执行所有拦截器的postHandle方法 
前面的步骤有任何异常都会直接触发afterCompletion 
页面成功渲染完成以后,也会倒叙触发afterCompletion 
 
文件上传 文件上传实现 如何接受表单中的提交的文件? 直接用@RequestPart()注解,然后用MultipartFile或者MultipartFile[] 来接收即可 然后复制文件到某个地方的话,用transferTo()方法,在里面new上一个File就可以了
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 package  com.zzmr.admin.controller;import  lombok.extern.slf4j.Slf4j;import  org.springframework.stereotype.Controller;import  org.springframework.web.bind.annotation.*;import  org.springframework.web.multipart.MultipartFile;import  java.io.File;import  java.io.IOException;@Controller @Slf4j public  class  FormTestController  {    @GetMapping("/form_layouts")      public  String form_layouts ()  {         return  "form/form_layouts" ;     }          @PostMapping("/upload")      public  String upload (@RequestParam("email")  String email,                           @RequestParam("username")  String username,                          @RequestPart("headerImg")  MultipartFile headImg,                          @RequestPart("photos")  MultipartFile[] photos)  throws  IOException {        log.info("上传的信息:email={},username={},headerImg={},photos={}" ,                 email, username, headImg.getSize(), photos.length);         if  (!headImg.isEmpty()) {                          String  originalFilename  =  headImg.getOriginalFilename();             headImg.transferTo(                     new  File ("D:\\Codefield\\springboot\\boot-05-web-admin\\src\\main\\resources\\file\\avatar\\"  + originalFilename));         }         if  (photos.length > 0 ) {             for  (MultipartFile photo : photos) {                 if  (!photo.isEmpty()) {                     String  originalFilename  =  photo.getOriginalFilename();                     photo.transferTo(new  File ("D:\\Codefield\\springboot\\boot-05-web-admin\\src\\main\\resources\\file\\lifePhoto\\"  + originalFilename));                 }             }         }         return  "main" ;     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <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" > Submit</button >                          </form >  
 
整体还是很简单的,需要注意的就是在多文件上传的地方:<input type="file" name="photos" multiple>,要用multiple来表示 
文件上传原理 文件上传的自动配置使用了MultipartAutoConfiguration–MultipartProperties
1 2 @ConfigurationProperties(prefix = "spring.servlet.multipart", ignoreUnknownFields = false) public  class  MultipartProperties  {}
 
也可以看出,在配置文件中更改spring.servlet.multipart就可以更改文件上传的一些参数,比如文件上传的最大的大小
自动配置好了StandardServletMultipartResolver  [文件上传解析器]
 
原理步骤
请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart)(返回MultipartHttpServletRequest)文件上传请求 
 
 2. 参数解析器来解析请求中的文件内容,封装成MultipartFile 3. 将request中文件信息封装为一个Map
 
 
FileCopyUtils 实现文件流的拷贝      <!--  -->     
异常处理 异常处理实现 默认规则 :
默认情况下,Spring Boot提供 /error处理所有错误的映射 
机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图(就是那个Whitelabel Error Page页面),以HTML格式呈现相同的数据机器客户端,比如postman  
 
1 2 3 4 5 6 7 {     "timestamp" :  "2022-12-24T11:53:47.024+00:00" ,      "status" :  404 ,      "error" :  "Not Found" ,      "message" :  "No message available" ,      "path" :  "/jkhjkghjk"  } 
 
要对其进行自定义,添加 View解析为 error 
要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加 ErrorAttributes类型的组件以使用现有机制但替换其内容。 
/templates/error/下的4xx,5xx页面会被自动解析 
 
这时所有的404和5xx都有自定义的页面了 
而且可以给这些页面回写数据 比如在5xx页面中
1 2 3 4 5 6 7 <section  class ="error-wrapper text-center" >     <h1 > <img  alt =""  src ="images/500-error.png" > </h1 >      <h2 > OOOPS!!!</h2 >      <h3  th:text ="${message}" > Something went wrong.</h3 >      <p  class ="nrml-txt"  th:text ="${trace}" > Why not try refreshing you page? Or you can <a  href ="#" > contact our support</a >  if the problem persists.</p >      <a  class ="back-btn"  th:href ="@{/main.html}" >  Back To Home</a >  </section > 
 
这时可以收到json中的信息,用于回写
异常处理原理 
自动配置 有没有发现,这个异常处理甚至什么都不用写,把页面放进去就行了,这是怎么配置的? 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19        @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)  public  ModelAndView errorHtml (HttpServletRequest request, HttpServletResponse response)  {	HttpStatus  status  =  getStatus(request); 	Map<String, Object> model = Collections 			.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); 	response.setStatus(status.value()); 	ModelAndView  modelAndView  =  resolveErrorView(request, response, status, model); 	return  (modelAndView != null ) ? modelAndView : new  ModelAndView ("error" , model); } @RequestMapping public  ResponseEntity<Map<String, Object>> error (HttpServletRequest request)  {	HttpStatus  status  =  getStatus(request); 	if  (status == HttpStatus.NO_CONTENT) { 		return  new  ResponseEntity <>(status); 	} 	Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); 	return  new  ResponseEntity <>(body, status); } 
 
如果想要返回页面;就会找error视图(StaticView)(默认是一个白页) 
定制错误处理逻辑 
自定义错误页
error/404.html,error/5xx/html,有精确的错误状态吗页面就匹配精确,没有就找4xx.html,如果都没有就出发白页 
 
 
@ControllerAdvice+@ExceptionHanlder处理全局异常; ExceptionHandlerExceptionResolver 支持的
 
@ResponseStatus+自定义异常; 底层是ResponseStatusExceptionResolver,把responseStatus注解的信息组装成ModelAndView返回;底层调用response.sendError(statusCode,resolvedReason) tomcat发送的/error
 
Spring底层的异常,如参数类型转换异常DefaultHandlerExceptionResolver处理框架底层的异常
response.sendError(HttpServletResponse.SC_BAD_REQUEST,ex.getMessage()); 
 
 
自定义实现HandlerExceptionResolver处理异常
 
 
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     package  com.zzmr.admin.exception; import  org.springframework.core.Ordered;import  org.springframework.core.annotation.Order;import  org.springframework.stereotype.Component;import  org.springframework.web.servlet.HandlerExceptionResolver;import  org.springframework.web.servlet.ModelAndView;import  javax.servlet.http.HttpServletRequest;import  javax.servlet.http.HttpServletResponse;import  java.io.IOException;@Order(value = Ordered.HIGHEST_PRECEDENCE)  @Component public  class  CustomerHandlerExceptionResolver  implements  HandlerExceptionResolver  {    @Override      public  ModelAndView resolveException (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)  {         try  {             response.sendError(511 ,"我不喜欢的错误" );         } catch  (IOException e) {             e.printStackTrace();         }         return  new  ModelAndView ();     } } 
 
ErrorViewResolver实现自定义处理异常
response.sendError,error请求就会转给controller 
你的异常没有任何人能处理,也是交给tomcat处理,error请求就会转给controller 
basicErrorController要去的页面地址 是ErrorViewResolver 
 
 
 
异常处理步骤流程 
执行目标方法,目标方法运行期间,有任何异常都会被catch;而且标志当前请求结束,并且用				dispatchException = ex处理
 
进入视图解析流程(页面渲染) processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
 
mv = processHandlerException(request, response, handler, exception); ;处理handler发生的异常,会返回一个mv
遍历所有的handlerExceptionResolvers,看谁能处理当前异常(HandlerExceptionResolver 处理器异常解析器) 
系统默认的异常解析器: 
 
 轮到第二个处理,里面有3个解析器
DefaultErrorAttributes 先来处理异常,把异常信息保存到request域中,并且返回null 
默认没有任何组件能处理异常,所以异常会被抛出 
如果没有任何组件能处理,最终底层会再发送一个/error请求,会被底层的BasicErrorController处理 
解析错误视图,遍历所有的errorViewResolver,看谁能解析 
默认的DefaultErrorViewResovler,作用就是把响应状态码作为错误页地址:error/5xx.html 
模板引擎最终响应这个页面:error/500.html 
 
 
 
 
 
Web原生组件注入(Servlet,Filter,Listener) 1. 使用Servlet API 1. 写一个Servlet,继承HttpServlet,然后添加@WebServlet(urlPatterns = “/my”)注解 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package  com.zzmr.admin.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("6" );     } } 
 
只这样写是不行的,需要在主方法类中加上@ServletComponentScan (basePackages = “com.zzmr.admin.servlet”)注解,也就是扫描某个包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  com.zzmr.admin;import  org.springframework.boot.SpringApplication;import  org.springframework.boot.autoconfigure.SpringBootApplication;import  org.springframework.boot.web.servlet.ServletComponentScan;@SpringBootApplication @ServletComponentScan(basePackages = "com.zzmr.admin.servlet") public  class  Boot05WebAdminApplication  {    public  static  void  main (String[] args)  {         SpringApplication.run(Boot05WebAdminApplication.class, args);     } } 
 
效果:直接响应,不经过Spring的拦截器处理 
还有Filter和Listener
Filter.java 
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 package  com.zzmr.admin.servlet;import  lombok.extern.slf4j.Slf4j;import  javax.servlet.*;import  javax.servlet.annotation.WebFilter;import  java.io.IOException;@Slf4j @WebFilter(urlPatterns = {"/css/*", "/images/*"}) public  class  MyFilter  implements  Filter  {    @Override      public  void  init (FilterConfig filterConfig)  throws  ServletException {         log.info("Filter初始化完成" );     }     @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("Filter已销毁" );     } } 
 
Listener 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package  com.zzmr.admin.servlet;import  lombok.extern.slf4j.Slf4j;import  javax.servlet.ServletContextEvent;import  javax.servlet.ServletContextListener;import  javax.servlet.annotation.WebListener;@WebListener @Slf4j public  class  MyServletContextListener  implements  ServletContextListener  {    @Override      public  void  contextInitialized (ServletContextEvent sce)  {       log.info("MyServletContextListener监听到项目初始化完成" );     }     @Override      public  void  contextDestroyed (ServletContextEvent sce)  {         log.info("监听到项目的销毁" );     } } 
 
都是加上对应的WebXXX注解,然后给主方法类加上那个扫描的注解就行了  也是推荐使用的
 
扩展:DispatcherServlet如何注册进来
 
容器中自动配置了DispatcherServlet属性绑定到WebMvcProperties,对应的配置文件配置项是:spring.mvc 
通过ServletRegistrationBean<DispatcherServlet>把DispathcerServlet配置进来 
默认映射的是/路径 
 
Tomcat-Servlet 多个Servlet都能处理同一层路径,就会遵守精确优选原则 A: /my/ B: /my/1
简单点说就是,系统会自动匹配最佳的,或者说最长最佳的tomcat中有/my/1,那发送/my/1就会来到tomcat,如果发送的是/my,那就会来到DispathcerServlet这里 
2. 使用RegistrationBean 官方文档 
you can use the ServletRegistrationBean , FilterRegistrationBean , and ServletListenerRegistrationBean  classes for complete control.
 
这时,Servlet,Filter以及Listener的类上就不用写WebXXX了 直接写一个类:
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 package  com.zzmr.admin.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(proxyBeanMethods = true) public  class  MyRegistryConfig  {    @Bean      public  ServletRegistrationBean myServlet ()  {         MyServlet  myServlet  =  new  MyServlet ();         return  new  ServletRegistrationBean (myServlet, "/my" , "/my2" );     }     @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  myServletContextListener  =  new  MyServletContextListener ();         return  new  ServletListenerRegistrationBean (myServletContextListener);     } } 
 
为什么自己写的原生Servlet不经过拦截器拦截? 
 
嵌入式Servlet容器 1. 切换嵌入式Servlet容器 官方文档 
就是,你有没有发现,现在都不用在项目里面配置tomcat了,又是改什么启动的浏览器,改上下文,改实例,一堆东西,现在全都配置好了,都不需要更改,这就是内嵌的Servlet容器,或者说是服务器,这个服务器是可以切换的.
 
默认支持的webServer
Tomcat,Jetty,or Undertow 
ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory并引导创建服务器 
 
 
切换服务器 
 
修改依赖,排除tomcat的依赖,添加undertow的依赖,然后就可以直接启动项目了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18         <dependency >              <groupId > org.springframework.boot</groupId >              <artifactId > spring-boot-starter-web</artifactId >              <exclusions >                  <exclusion >                      <groupId > org.springframework.boot</groupId >                      <artifactId > spring-boot-starter-tomcat</artifactId >                  </exclusion >              </exclusions >          </dependency >          <dependency >              <groupId > org.springframework.boot</groupId >              <artifactId > spring-boot-starter-undertow</artifactId >          </dependency >  
 
原理
SpringBoot应用启动发现当前是Web应用,web场景包-导入tomcat 
web应用会创建一个web版的ioc容器ServletWebServerApplicationContext  
ServletWebServerApplicationContext 启动的时候寻找ServletWebServerFactory(Servlet的web服务器工厂—>Servlet的web服务器) 
SpringBoot底层默认有很多的WebServer工厂 
TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory 
底层直接会有一个自动配置类ServletWebServerFactoryAutoConfiguration  
ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration 
ServletWebServerFactoryConfiguration配置类 根据动态判断系统中到底导入了哪个Web服务器的包(默认是web-starter导入tomcat包) 容器中就有TomcatServletWebServerFactory  
TomcatServletWebServerFactory创建Tomcat服务器并启动 
内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在) 
 
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 	static  class  EmbeddedTomcat  { 	@Bean  	TomcatServletWebServerFactory tomcatServletWebServerFactory (  			ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, 			ObjectProvider<TomcatContextCustomizer> contextCustomizers, 			ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers)  {		TomcatServletWebServerFactory  factory  =  new  TomcatServletWebServerFactory (); 		factory.getTomcatConnectorCustomizers() 				.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList())); 		factory.getTomcatContextCustomizers() 				.addAll(contextCustomizers.orderedStream().collect(Collectors.toList())); 		factory.getTomcatProtocolHandlerCustomizers() 				.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList())); 		return  factory; 	} } @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) static  class  EmbeddedJetty  {	@Bean  	JettyServletWebServerFactory JettyServletWebServerFactory (  			ObjectProvider<JettyServerCustomizer> serverCustomizers)  {		JettyServletWebServerFactory  factory  =  new  JettyServletWebServerFactory (); 		factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList())); 		return  factory; 	} } @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) static  class  EmbeddedUndertow  {	@Bean  	UndertowServletWebServerFactory undertowServletWebServerFactory (  			ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers, 			ObjectProvider<UndertowBuilderCustomizer> builderCustomizers)  {		UndertowServletWebServerFactory  factory  =  new  UndertowServletWebServerFactory (); 		factory.getDeploymentInfoCustomizers() 				.addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList())); 		factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList())); 		return  factory; 	} 	@Bean  	UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer (  			ServerProperties serverProperties)  {		return  new  UndertowServletWebServerFactoryCustomizer (serverProperties); 	} } 
 
 
2. 定制Servlet容器 
实现WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
把配置文件的值和ServletWebServerFactory 进行绑定 
 
 
修改配置文件server.xxx
 
直接自定义ConfigurableServletWebServerFactory(很帅 ) 
 
xxxxCustomizer:定制化器,可以改变xxx的默认规则 
 
定制化原理 定制化的常见方式 
修改配置文件 
xxxxCustomizer; 
编写自定义的配置类 xxxConfiguration + @Bean替换,增加容器中默认组件;视图解析器 
web应用实现WebMvcConfigurer即可定制化web功能 
 
1 2 @Configuration public  class  AdminWebConfig  implements  WebMvcConfigurer  {}
 
@EnableWebMvc + WebMvcConfigurer — @Bean  可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能(高级功能,初学者退避三舍 )。
原理:
WebMvcAutoConfiguration默认的SpringMVC的自动配置功能类,如静态资源、欢迎页等。 
一旦使用 @EnableWebMvc ,会 @Import(DelegatingWebMvcConfiguration.class)。 
DelegatingWebMvcConfiguration的作用,只保证SpringMVC最基本的使用
把所有系统中的 WebMvcConfigurer拿过来,所有功能的定制都是这些 WebMvcConfigurer合起来一起生效。 
自动配置了一些非常底层的组件,如 RequestMappingHandlerMapping,这些组件依赖的组件都是从容器中获取如。 
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport。 
 
 
WebMvcAutoConfiguration里面的配置要能生效必须  @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)。 
@EnableWebMvc 导致了WebMvcAutoConfiguration  没有生效。 
 
 
 
 
 
原理分析套路 场景starter-xxxAutoConfiguration-导入xxx组件-绑定xxxProperties–绑定配置文件项 
6天,看到这了
 
数据访问 1. 数据源的自动配置 
先导入jdbc场景 
 
1 2 3 4 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-data-jdbc</artifactId >  </dependency > 
 
 2. 数据库驱动没导,为什么呢? 因为SpringBoot不知道要使用哪个数据库,所以我们要使用哪个数据库,就导入哪个驱动就行了 SpringBoot有mysql的版本仲裁(数据库的版本要和驱动版本相同),默认都是8.xx了,我装的也是mysql8,
1 2 3 4 <dependency >     <groupId > mysql</groupId >      <artifactId > mysql-connector-java</artifactId >  </dependency > 
 
如果想改的话,两种办法
<version>5.xxx</version> 
重新声明(maven属性的就近优先原则) 
 
1 2 3 4     <properties >          <java.version > 1.8</java.version >      </properties >  
 
分析自动配置 
自动配置的类
DataSourceAutoConfiguration 数据源的自动配置
修改数据源相关的配置:spring.datasource 
数据库连接池的配置,是自己容器中没有DataSource才自动配置的 
底层配置好的连接池是HikariDataSource 
 
 
 
1 2 3 4 5 6 7 8 9     	@Configuration(proxyBeanMethods = false)  @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, 		DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, 		DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected  static  class  PooledDataSourceConfiguration  {} 
 
DataSourceTransactionManagerAutoConfiguration 事务管理器的自动配置 
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,之前学spring用过这个东西,也挺好用 
 
 
 
修改配置项 
1 2 3 4 5 6 spring:   datasource:      url:  jdbc:mysql://localhost:3306/db_account?serverTimezone=UTC      username:  root      password:  "010203"      driver-class-name:  com.mysql.cj.jdbc.Driver  
 
为什么?密码不加双引号,就一直错误
然后测试一下
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.zzmr.admin;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;@SpringBootTest @Slf4j class  Boot05WebAdminApplicationTests  {    @Autowired      JdbcTemplate jdbcTemplate;     @Test      void  contextLoads ()  {         Long  aLong  =  jdbcTemplate.queryForObject("select count(*) from account_tbl" ,Long.class);         log.info("记录总数{}" ,aLong);     } } 
 
没问题啊没问题
2. 使用Druid数据源 自定义整合 官方网址 
引入依赖: 
 
1 2 3 4 5 <dependency >     <groupId > com.alibaba</groupId >      <artifactId > druid</artifactId >      <version > 1.2.5</version >  </dependency > 
 
创建配置类 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package  com.zzmr.admin.config;import  com.alibaba.druid.pool.DruidDataSource;import  org.springframework.boot.context.properties.ConfigurationProperties;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;import  javax.sql.DataSource;@Configuration public  class  MyDataSourceConfig  {         @Bean      @ConfigurationProperties("spring.datasource")      public  DataSource dataSource ()  {         DruidDataSource  dataSource  =  new  DruidDataSource ();         return   dataSource;     } } 
 
因为配置文件中已经有各个属性了,所以直接绑定配置项就行了,在yml配置文件中关于数据源的配置项就是spring.datasource
开启监控功能 这个还真没接触过 可以监控sql的一些内容
 使用步骤:
开启监控功能 在配置druid数据源的地方给set一个stat 
 
1 2 dataSource.setFilters("stat" ); 
 
配置监控功能 
 
1 2 3 4 5 6 7 8 9 10 11 12 @Bean public  ServletRegistrationBean StatViewServlet ()  {    StatViewServlet  statViewServlet  =  new  StatViewServlet ();     ServletRegistrationBean<StatViewServlet> registrationBean =             new  ServletRegistrationBean <>(statViewServlet, "/druid/*" );     return  registrationBean; } 
 
直接来使用就行了 访问localhost:8080/druid/index.html 
 
配置WebStatFilter
1 2 3 4 5 6 7 8 9 10 11 @Bean public  FilterRegistrationBean WebStatFilter ()  {    WebStatFilter  webStatFilter  =  new  WebStatFilter ();     FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new  FilterRegistrationBean <>(webStatFilter);     filterRegistrationBean.setUrlPatterns(Arrays.asList("/*" ));     filterRegistrationBean.addInitParameter("exclusions" , "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" );     return  filterRegistrationBean; } 
 
还有配置防火墙
1 dataSource.setFilters("stat,wall" ); 
 
只需要在setFilters里面加上wall就可以了
给监控页配置账号和密码 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Bean public  ServletRegistrationBean StatViewServlet ()  {    StatViewServlet  statViewServlet  =  new  StatViewServlet ();     ServletRegistrationBean<StatViewServlet> registrationBean =             new  ServletRegistrationBean <>(statViewServlet, "/druid/*" );     registrationBean.addInitParameter("loginUsername" , "admin" );     registrationBean.addInitParameter("loginPassword" , "010203" );     return  registrationBean; } 
 
只需要在registrationBean中add就行了
?还有优化的写法,其实我感觉这样写也没有多麻烦啊哈哈哈哈艹
druid数据源stater整合方式 这种才是以后以后用的方式啊哈哈哈哈艹
前面全都注掉,然后在pom.xml中把druid的依赖也注掉
加上:
1 2 3 4 5 <dependency >     <groupId > com.alibaba</groupId >      <artifactId > druid-spring-boot-starter</artifactId >      <version > 1.1.17</version >  </dependency > 
 
分析自动配置 
扩展配置项: spring.datasource.druid @Import({ 
DruidSpringAopConfiguration.class,    监控SpringBean的,配置项:spring.datasouce.druid.aop-patterns 
DruidStatViewServletConfiguration.class,   监控页的配置 spring.datasource.druid.stat-view-servlet,默认开启 
DruidWebStatFilterConfiguration.class, web监控配置, 配置项:spring.datasource.druid.web-stat-filter  默认开启 
DruidFilterConfiguration.class   所有Druid自己的filter的配置 }) 
 
1 2 3 4 5 6 7 8 9 private  static  final  String  FILTER_STAT_PREFIX  =  "spring.datasource.druid.filter.stat" ;private  static  final  String  FILTER_CONFIG_PREFIX  =  "spring.datasource.druid.filter.config" ;private  static  final  String  FILTER_ENCODING_PREFIX  =  "spring.datasource.druid.filter.encoding" ;private  static  final  String  FILTER_SLF4J_PREFIX  =  "spring.datasource.druid.filter.slf4j" ;private  static  final  String  FILTER_LOG4J_PREFIX  =  "spring.datasource.druid.filter.log4j" ;private  static  final  String  FILTER_LOG4J2_PREFIX  =  "spring.datasource.druid.filter.log4j2" ;private  static  final  String  FILTER_COMMONS_LOG_PREFIX  =  "spring.datasource.druid.filter.commons-log" ;private  static  final  String  FILTER_WALL_PREFIX  =  "spring.datasource.druid.filter.wall" ;private  static  final  String  FILTER_WALL_CONFIG_PREFIX  =  FILTER_WALL_PREFIX + ".config" ;
 
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 spring:   datasource:      url:  jdbc:mysql://localhost:3306/db_account?serverTimezone=UTC      username:  root      password:  "010203"      driver-class-name:  com.mysql.cj.jdbc.Driver      druid:        filters:  stat,wall          aop-patterns:  com.zzmr.admin.*           stat-view-servlet:            enabled:  true          login-username:  admin          login-password:  '010203'          reset-enable:  false        web-stat-filter:           enabled:  true          url-pattern:  /*          exclusions:  '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'        filter:          stat:              slow-sql-millis:  1000            log-slow-sql:  true            enabled:  true          wall:            enabled:  true            config:              drop-table-allow:  false  
 
配置文件如上参考链接 
整合MyBatis操作 不容易啊,终于看到整合MyBatis了
引入starter 
 
1 2 3 4 5 <dependency >     <groupId > org.mybatis.spring.boot</groupId >      <artifactId > mybatis-spring-boot-starter</artifactId >      <version > 2.1.4</version >  </dependency > 
 
引入成功:
配置模式 
全局配置模式 
SqlSessionFactory  自动配置好了 
SqlSession         自动配置了SqlSessionTemplate组合了SqlSession 
Mapper             只要写的操作MyBatis的接口标注了@Mapper注解,就会被自动扫描进来 
 
1 2 3 @EnableConfigurationProperties(MybatisProperties.class)  @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) public  class  MybatisAutoConfiguration {}
 
MyBatisProperties.class
1 2 3 4 @ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX) public  class  MybatisProperties  {  public  static  final  String  MYBATIS_PREFIX  =  "mybatis" ;} 
 
所以得出,可以修改配置文件中mybatis开始的所有;
配置MyBatis的规则 
1 2 3 4 mybatis:   config-location:  classpath:mybatis/mybatis-config.xml     mapper-locations:  classpath:mybatis/mapper/*.xml   
 
具体使用还是和之前一样 先是写mapper接口的映射文件
1 2 3 4 5 6 7 8 9 <?xml version="1.0"  encoding="UTF-8"  ?>  <!DOCTYPE mapper  PUBLIC  "-//mybatis.org//DTD Mapper 3.0//EN"          "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper  namespace ="com.zzmr.admin.mapper.AccountMapper" >     <select  id ="getAcct"  resultType ="com.zzmr.admin.bean.Account" >          select * from account_tbl where user_id = #{id}     </select >  </mapper > 
 
然后直接调用接口就行了
这里的全局配置文件还是可以用的,比如可以在里面加上驼峰的映射
1 2 3 4 <settings >          <setting  name ="mapUnderscoreToCamelCase"  value ="true" />  </settings > 
 
配置private Configuration configuration; mybatis.configuration 下面的所有,就是相当于改mybatis的全局配置文件中的值
所以这个驼峰映射,也可以在yml中配置:
1 2 3 4 5 6 mybatis:   config-location:  classpath:mybatis/mybatis-config.xml    mapper-locations:  classpath:mybatis/mapper/*.xml    configuration:      map-underscore-to-camel-case:  true  
 
最后两句就是 然后就报错了..
 意思就是,全局配置文件和这个configuration配置项是不能同时存在的 所以,如果想要使用yml的方式,就需要把上面的config-location注掉,如:
1 2 3 4 5 6 mybatis:    y:  classpath:mybatis/mapper/*.xml    configuration:      map-underscore-to-camel-case:  true  
 
所以,可以不写配置文件,全局配置文件的内容全部放在configuration下  这是最重要的.以后都用不到配置文件了
几步走
 
导入mybatis官方starter 
编写mapper接口,标注@Mapper 
编写sql映射文件并绑定mapper接口 
在application.yml中指定Mapper配置文件的位置,以及(指定全局配置文件的信息,但是不常用了)配置mybatis.configuration,配置之前全局配置文件的内容  
 
注解模式 纯注解配置MyBatis
1 2 3 4 5 6 7 8 9 10 11 12 13 package  com.zzmr.admin.mapper;import  com.zzmr.admin.bean.City;import  org.apache.ibatis.annotations.Mapper;import  org.apache.ibatis.annotations.Select;@Mapper public  interface  CityMapper  {    @Select("select * from city where id = #{id")      public  City getById (Long id) ; } 
 
这直接连mapper文件都省去了
测试:
1 2 3 4 5 6 7 8 @Autowired CityService cityService; @GetMapping("/city") @ResponseBody public  City getCityById (@RequestParam("id")  Long id) {    return  cityService.getById(id); } 
 
也没有问题
混合模式 以后应该是混合模式比较多吧
就是混合到一块啊,互不影响
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  com.zzmr.admin.mapper;import  com.zzmr.admin.bean.City;import  org.apache.ibatis.annotations.Mapper;import  org.apache.ibatis.annotations.Select;@Mapper public  interface  CityMapper  {    @Select("select * from city where id = #{id}")      public  City getById (Long id) ;     public  void  insert (City city) ; } 
 
然后insert方法就是使用的xml
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0"  encoding="UTF-8"  ?>  <!DOCTYPE mapper  PUBLIC  "-//mybatis.org//DTD Mapper 3.0//EN"          "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper  namespace ="com.zzmr.admin.mapper.CityMapper" >     <insert  id ="insert"  keyProperty ="id"  useGeneratedKeys ="true" >          insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})     </insert >     </mapper > 
 
也是一样能使用的
那要是就是要用注解怎么办? 
1 2 3 @Insert("insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})") @Options(useGeneratedKeys = true,keyProperty = "id") public  void  insert (City city) ;
 
有一个@Options注解,就是用来放上面注解的属性的
整合MyBatis的最佳实战 
 
引入mybatis-stater 
配置application.yaml中,指定mapper-location位置即可 
编写mapper接口,并标注@Mapper注解 
简单方法直接注解方式 
复杂方法编写mapper.xml进行绑定映射 
 
还有一个@MapperScan()注解,用于扫描mapper接口,有了这个注解,就不需要在每个mapper接口上写上@Mapper注解了 
@MapperScan(“com.zzmr.admin.mapper”) 
整合MyBatis-Plus完成CRUD 还没学过MyBatis-Plus哎 MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发,提高效率而生官方文档 
安装一个MyBatisX插件 ,直接搜就行了
整合及使用 引入依赖 
1 2 3 4 5 <dependency >     <groupId > com.baomidou</groupId >      <artifactId > mybatis-plus-boot-starter</artifactId >      <version > 3.4.1</version >  </dependency > 
 
自动配置 MybatisPlusAutoConfiguration配置类,MybatisPlusProperties配置项绑定
 
1 String  MYBATIS_PLUS  =  "mybatis-plus" ;
 
所以配置mybatis-plus的内容都是在配置文件中修改mybatis-plus:xxx
SqlSessionFactory自动配置好了,底层是容器中默认的数据源 
mapperLocations自动配置好了,有默认值: classpath*:/mapper/**/*.xml 任意包的类路径下的所有 mapper文件夹下任意路径下的所有xml都是sql映射文件,建议以后sql映射文件,放在mapper下 
容器中也自动配置好了有SqlSessionTemplate 
@Mapper 标注的接口也会被自动扫描,建议直接使用@MapperScan(“com.xxx.mapper”)批量扫描包 不是,上一集还是不推荐来着哈哈哈哈 无所谓,人家说是就是啥 
 
1 2 3 4 @Bean @ConditionalOnMissingBean public  SqlSessionFactory sqlSessionFactory (DataSource dataSource)  throws  Exception {} 
 
优点 
只需要我们的Mapper继承BaseMapper就可以拥有CRUD能力 
 
Talk is cheap. Show me the code.
 
注意一点,就是MyBatis-Plus默认的是bean类的全部属性都要求在数据库中,如果不是一一对应的就会报错 此时可以使用    @TableField(exist = false)来指定某些属性不是数据库中的列
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 package  com.zzmr.admin.bean;import  com.baomidou.mybatisplus.annotation.TableField;import  lombok.AllArgsConstructor;import  lombok.Data;import  lombok.EqualsAndHashCode;import  lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode public  class  User  {         @TableField(exist = false)      private  String userName;     @TableField(exist = false)      private  String password;          private  Long id;     private  String name;     private  Integer age;     private  String email; } 
 
还有表名和类名不一致的情况,使用@TableName()指定表名就行了 
? 这个MyBatis也太强了吧 现在连Service都不怎么用写了 直接让Service接口继承IService<User>
1 2 3 4 5 6 7 package  com.zzmr.admin.service;import  com.baomidou.mybatisplus.extension.service.IService;import  com.zzmr.admin.bean.User;public  interface  UserService  extends  IService <User> {} 
 
然后在实现类里:
1 2 3 4 5 6 7 8 9 10 11 12 package  com.zzmr.admin.service.impl;import  com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import  com.zzmr.admin.bean.User;import  com.zzmr.admin.mapper.UserMapper;import  com.zzmr.admin.service.UserService;import  org.springframework.stereotype.Service;@Service public  class  UserServiceImpl  extends  ServiceImpl <UserMapper, User> implements  UserService  {} 
 
那这个Service就有了很多方法:
然后想获取全部用户数据:
1 2 List<User> list = userService.list(); model.addAttribute("users" , list); 
 
list()就是获取全部的数据 然后就能去到了,这也太强了
这里又写了一遍分页 但是用的方法更牛逼
还是先来控制器方法,用于返回dynamic_table页面的数据
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     @GetMapping("/dynamic_table")      public  String dynamic_table (Model model,@RequestParam(value = "pn",defaultValue = "1")  Integer pn)  {                           List<User> list = userService.list();                           Page<User> userPage = new  Page <>(pn, 2 );                  Page<User> page = userService.page(userPage, null );         model.addAttribute("page" ,page);         return  "table/dynamic_table" ;     } 
 
dynamic_table.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 <table  class ="display table table-bordered table-striped"  id ="dynamic-table" >                                     <thead >                                      <tr >                                          <th > #</th >                                          <th > id</th >                                          <th > name</th >                                          <th > age</th >                                          <th > email</th >                                          <th > 操作</th >                                      </tr >                                      </thead >                                      <tbody >                                      <tr  class ="gradeX"  th:each ="user,stat : ${page.records}" >                                          <td  th:text ="${stat.count}" > </td >                                          <td  th:text ="${user.id}" > </td >                                          <td  th:text ="${user.name}" > </td >                                          <td  th:text ="${user.age}" > </td >                                          <td  th:text ="${user.email}" > </td >                                          <td > X</td >                                      </tr >                                      </tbody >                                      <tfoot >                                      </tfoot >                                  </table >                                  <div  class ="row" >                                      <div  class ="col-lg-6" >                                          <div  class ="dataTables_info"  id ="editable-sample_info" > 当前第                                             [[${page.current}]] 页 总计 [[${page.pages}]] 页                                             共 [[${page.total}]] 条记录                                         </div >                                      </div >                                      <div  class ="col-lg-6" >                                          <div  class ="dataTables_paginate paging_bootstrap pagination" >                                              <ul >                                                  <li  class ="prev disabled" > <a  href ="#" > ← Prev</a > </li >                                                  <li  th:class ="${num == page.current?'active':''}"                                                       th:each ="num:${#numbers.sequence(1,page.pages)}" >                                                     <a  th:href ="@{/dynamic_table(pn=${num})}" > [[${num}]]</a >                                                  </li >                                                  <li  class ="next" > <a  href ="#" > Next → </a > </li >                                              </ul >                                          </div >                                      </div >                                  </div >  
 
重要的是那三个li标签 之前都没这样写过 是真的厉害
当然还有开启分页功能的配置类:
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.zzmr.admin.config;import  com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import  com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;@Configuration public  class  MyBatisConfig  {    @Bean      public  MybatisPlusInterceptor mybatisPlusInterceptor ()  {         MybatisPlusInterceptor  mybatisPlusInterceptor  =  new  MybatisPlusInterceptor ();                  PaginationInnerInterceptor  paginationInnerInterceptor  =  new  PaginationInnerInterceptor ();         paginationInnerInterceptor.setOverflow(true );         paginationInnerInterceptor.setMaxLimit(500L );         mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);         return  mybatisPlusInterceptor;     } } 
 
删除操作 
1 2 3 4 5 @GetMapping("/user/delete/{id}") public  String deleteUser (@PathVariable("id")  Long id) {    userService.removeById(id);     return  "redirect:/dynamic_table" ; } 
 
也是直接调用方法了,service和mapper都不用写东西
然后页面
1 2 3 <td >       <a  th:href ="@{/user/delete/{id}(id=${user.id})}"  class ="btn btn-danger btn-sm"  type ="button" > 删除</a >  </td > 
 
其实这个写法我感觉复杂了 以前的写法,直接拼接请求路径
1 <a  th:href ="@{'/user/delete/'+${user.id}}"  class ="btn btn-danger btn-sm"  
 
但是想删除的同时返回原来的那一页怎么办?之前都是删除完返回第一页 可以将pn(页号)也拼接到路径中
1 <a  th:href ="@{'/user/delete/'+${user.id}+'?pn='+${page.current}}"  class ="btn btn-danger btn-sm"  
 
然后控制器方法也要修改
1 2 3 4 5 6 7 8 @GetMapping("/user/delete/{id}") public  String deleteUser (@PathVariable("id")  Long id,                          @RequestParam(value = "pn", defaultValue = "1")  Integer pn,                          RedirectAttributes ra)  {    userService.removeById(id);     ra.addAttribute("pn" ,pn);     return  "redirect:/dynamic_table" ; } 
 
RedirectAttributes 可以将指定的参数拼接到重定向的路径后面 
然后 然后就结束了
redis也没学啊,不管了,先看着
NoSQL 
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。redis官网 
 
要不,就先看着?
引入场景:
1 2 3 4 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-data-redis</artifactId >  </dependency > 
 
自动配置
RedisAutoConfiguration 自动配置类  RedisProperties 属性类—>spring.redis.xxx是对reids的配置 
连接工厂是准备好的-LettuceConnectionConfiguration,JedisConnectionConfiguration两个 
自动注入了RedisTemplate<Object,Object> xxxTemplate; 
自动注入了StringRedisTemplate,k和v都是String 
key:value 
底层只要使用RedisTemplate和StringRedisTemplate就可以操作redis 
 
环境搭建 
购买一个redis云数据库,按量付费 
申请redis的公网连接地址 ?不行啊,买不了经典网络的,申请不了公网连接 算了,这一块随便听听吧,等后面把redis学完再说 
 
单元测试 Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
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) 
JUnit 5’s Vintage已经从 spring-boot-starter-test从移除。如果需要继续兼容Junit4需要自行引入Vintage依赖: 
 
1 2 3 4 5 6 7 8 9 10 11 <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注解,然后跟以前一样写测试方法就行了
而依赖,默认也是配好的
1 2 3 4 5 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-test</artifactId >      <scope > test</scope >  </dependency > 
 
Junit5 注解 
@DisplayName(“xxx”)  为测试类或者测试方法设置展示名称 标注在方法上(也可以标注在类上) 然后这个测试方法就有了名字: 
 
1 2 3 4 5 @DisplayName("测试displayName注解") @Test void  testDisplayName () {    System.out.println(1 ); } 
 
@BeforeEach  表示在每个单元测试之前执行 标注了这个注解,那么测试其他方法的时候,它就会先执行 
 
1 2 3 4 @BeforeEach void  testBeforeEach () {    System.out.println("测试就要开始了" ); } 
 
@AfterEach  在每个测试方法结束之后执行 
 
1 2 3 4 @AfterEach void  testAfterEach ()  {    System.out.println("测试就要结束了" ); } 
 
@BeforeAll和@AfterAll 类中所有的测试方法都执行开始或者结束时执行 
 
1 2 3 4 5 6 7 8 9 @BeforeAll static  void  testBeforeAll ()  {    System.out.println("所有测试就要开始了" ); } @AfterAll static  void  testAfterAll ()  {    System.out.println("所有测试就要结束了" ); } 
 
@Timeout 
 
1 2 3 4 5 6 7 8 9 @Test @Timeout(value = 500,unit = TimeUnit.MICROSECONDS) void  testTimeout ()  throws  InterruptedException {    Thread.sleep(500 ); } 
 
@RepeatedTest 
 
1 2 3 4 5 @RepeatedTest(5) @Test void  test3 () {    System.out.println(5 ); } 
 
断言(assertions) 断言(assertions)时测试方法中的核心部分,用来对测试需要满足的条件进行验证,这些断言方法都是org.junit.jupiter.api.Assertions的静态方法 ,Junit5内置的断言可以分成如下几个类别: 检察业务逻辑返回的数据是否合理所有的测试运行结束以后,会有一个详细的测试报告 
1. 简单断言 用来对单个值进行简单的验证,如:
assertEquals(expected,actual,String) ,判断值是否相等,第一个参数是期望的值,第二个是实际的值 
 
1 2 3 4 5 6 7 8 9 10 11 @DisplayName("测试简单断言") @Test void  testSimpleAssertions ()  {    int  cal  =  cal(3 , 3 );          assertEquals(5 , cal,"业务逻辑计算失败" ); } int  cal (int  i, int  j)  {    return  i + j; } 
 
如果相等,没问题,如果不等,则会提示自定义的信息
assertSame(obj1,obj2,String) ,判断两个对象是否相同 
 
1 2 3 Object  obj1  =  new  Object ();Object  obj2  =  new  Object ();assertSame(obj1, obj2, "两个对象不一样" ); 
 
结果:
注意,断言的情况下,前面的断言失败,后面的断言就不会再执行了 
 
2. 数组断言 通过assertArrayEquals方法来判断两个对象或原始类型的数组是否相等
1 2 3 4 5 @Test @DisplayName("array assertion") public  void  array ()  {    assertArrayEquals(new  int []{1 , 2 }, new  int []{2 , 1 },"数组内容不相等" ); } 
 
3. 组合断言 1 2 3 4 5 6 7 8 9 10 @Test @DisplayName("组合断言") void  all ()  {         assertAll("test" ,             () -> assertTrue(false ,"结果不为true" ),             ()-> assertEquals(1 ,2 ,"结果不相等" )); } 
 
4. 异常断言 1 2 3 4 5 6 7 8 @Test @DisplayName("异常断言") void  testException ()  {    assertThrows(ArithmeticException.class,             () -> {                 int  i  =  10  / 1 ;             }, "业务逻辑居然正常运行" ); } 
 
这个比较怪,前面的是期望出现异常,如果没有出现异常,就会报错 出现了异常,就没问题
5. 超时断言 1 2 3 4 5 6 @Test @DisplayName("超时测试") public  void  timeoutTest ()  {         Assertions.assertTimeout(Duration.ofMillis(1000 ), () -> Thread.sleep(500 )); } 
 
6. 快速失败 1 2 3 4 5 6 7 8 @Test @DisplayName("快速失败") void  testFail () {         if  (2 ==2 ){         fail("测试失败" );     } } 
 
绷不住了 哈哈哈哈哈哈
前置条件(assumptions) Junit5中的前置条件(assumptions[假设])类似断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止,前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要 
1 2 3 4 5 6 7 8 9 @Test @DisplayName("测试前置条件") void  testAssumptions ()  {    Assumptions.assumeTrue(false , "结果不是true" );     System.out.println("11111" ); } 
 
嵌套测试 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 package  com.zzmr.admin;import  org.junit.jupiter.api.BeforeEach;import  org.junit.jupiter.api.DisplayName;import  org.junit.jupiter.api.Nested;import  org.junit.jupiter.api.Test;import  java.util.EmptyStackException;import  java.util.Stack;import  static  org.junit.jupiter.api.Assertions.*;@DisplayName("A stack") class  TestingAStackDemo  {    Stack<Object> stack;     @Test      @DisplayName("is instantiated with new Stack()")      void  isInstantiatedWithNew ()  {         new  Stack <>();                           assertNull(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());             }         }     } } 
 
参数化测试 参数化测试时Junit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省却了很多冗余代码
@ValueSource:为参数化测试指定入参来源,支持八大基础类型以及String类型Class类型 
@NullSource:表示为参数化测试提供一个null的入参 
@EnumSource:表示为参数化测试提供一个枚举入参 
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参 
@MethodSource:表示读取指定方法的反沪指作为参数化测试入参(注意方法返回需要是一个流) 
 
@ValueSource测试用例
1 2 3 4 5 6 @ParameterizedTest @DisplayName("参数化测试") @ValueSource(ints = {1, 2, 3, 4, 5}) void  testParameterized (int  i)  {    System.out.println(i); } 
 
测试结果:
还有@MethodSource
1 2 3 4 5 6 7 8 9 10 @ParameterizedTest @DisplayName("参数化测试2") @MethodSource("stringStreamProvider") void  testParameterized2 (String i)  {    System.out.println(i); } static  Stream<String> stringStreamProvider ()  {    return  Stream.of("apple" , "banana" ); } 
 
指标监控 没听过的名词
1. SpringBoot Actuatot 简介 未来每一个微服务在云上部署以后,我们都需要对其进行监控,追踪,审计,控制等,SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控,审计等功能
使用 引入场景
1 2 3 4 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-actuator</artifactId >  </dependency > 
 
访问http://localhost:8080/actuator/ **
有关actuator的配置:
1 2 3 4 5 6 7 8 management:   endpoints:      enabled-by-default:  true       web:        exposure:          include:  '*'    
 
暴露所有的端点后,就可以在浏览器访问了,测试bean
具体有哪些可以访问点击查看官方文档 
最常用的:http://localhost:8080/actuator/metrics  然后呢,想访问具体的某一项 就可以在后面拼上路径http://localhost:8080/actuator/metrics/jvm.buffer.memory.used 
Actuator Endpoint 
最常用的端点 
 
Health  :监控状况 
Metrics  :运行时指标 
Loggers  :日志记录 
 
Health Endpoint  健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一些列组件健康状况的集合 重要的几点:
health endpoint 返回的结果,应该是一系列健康检查后的一个汇总报告 
很多的健康检查默认已经自动配置好了,比如:数据库,redis等 
可以很容易的添加自定义的健康检查机制 
 
但是我们直接访问,只会返回一个up/down 那怎么显示详细信息呢?
1 2 3 endpoint:   health:      show-details:  always  
 
配置上这个
Metrics Endpoint  提供详细的,层级的,空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到
通过Metrics对接多种监控系统 
简化核心Metrics开发 
添加自定义Metrics或者扩展已有的Metrics 
 
但是,开启所有的端点,可能会不太安全 所以可以默认关闭所有的端点 需要哪些再开
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 management:   endpoints:      enabled-by-default:  false       web:        exposure:          include:  '*'      endpoint:      health:        show-details:  always        enabled:  true      info:        enabled:  true      beans:        enabled:  true      metrics:        enabled:  true  
 
就比如这样,在每个端点里加上enabled: true就行了
定制Endpoint 定制Health信息 
写一个配置类:
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.zzmr.admin.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);     } } 
 
此时再查看health,就能看到myCom
定制info信息 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package  com.zzmr.admin.acutator.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" , "zzmr" )                 .withDetails(Collections.singletonMap("world" , "666" ));     } } 
 
也可以修改配置文件,但是不出来
 
定制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 package  com.zzmr.admin.acutator.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("dockInfo" ,"docker started..." );     }     @WriteOperation      public  void  stop () {         System.out.println("docker stopper..." );     } } 
 
这个自定义的断电不参与配置文件中的开启或者关闭
可视化 竟然新建了一个项目 只选中了web一个场景 导入了一个场景:
1 2 3 4 5 <dependency >     <groupId > de.codecentric</groupId >      <artifactId > spring-boot-admin-starter-server</artifactId >      <version > 2.7.9</version >  </dependency > 
 
给主方法类上加上一个注解:@EnableAdminServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  com.zzmr.boot;import  de.codecentric.boot.admin.server.config.EnableAdminServer;import  org.springframework.boot.SpringApplication;import  org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication @EnableAdminServer public  class  Boot05AdminserverApplication  {    public  static  void  main (String[] args)  {         SpringApplication.run(Boot05AdminserverApplication.class, args);     } } 
 
然后,就直接运行项目 访问http://localhost:8888/applications  就行了
哦,这个新建的项目属于是一个服务器,专门用于监控
然后又回到原来的项目 此时导入依赖
1 2 3 4 5 <dependency >     <groupId > de.codecentric</groupId >      <artifactId > spring-boot-admin-starter-client</artifactId >      <version > 2.7.9</version >  </dependency > 
 
这个属于是客户端依赖:client
然后到配置文件中:
1 2 3 4 5 6 boot:   admin:      client:        url:  http://localhost:8888        instance:          prefer-ip:  true  
 
就是指定要发送到哪个url里(服务器在哪)
记得要开启那个全部端点 
原理解析 到最后了,看看能不能今天晚上就看完(2022年12月27日 18点33分)
Profile功能 为了方便多环境适配,springboot简化了profile功能
说白了就是写多套配置文件,生产环境一套,开发环境一套,切换的时候比较方便,不用一个一个改官方文档 
application-profile功能 具体实现:
创建一个项目,然后创建一个controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package  com.example.boot.controller;import  org.springframework.beans.factory.annotation.Value;import  org.springframework.web.bind.annotation.GetMapping;import  org.springframework.web.bind.annotation.RestController;@RestController public  class  HelloController  {    @Value("${person.name:李四}")      private  String name;     @GetMapping("/")      public  String hello ()  {         return  "hello "  + name;     } } 
 
实现的功能就是,name从配置文件中取,@Value(“${person.name:李四}”),就代表从配置文件中取,没有值的话,就用默认值李四
然后写两个配置文件:
application-prod.yml(生产环境的配置文件)
 
application-test.yml(测试环境的配置文件)
 
这时,直接访问/会提示是李四,因为是使用的默认值
如果在properties中更改为:spring.profiles.active=prod 就代表使用prod生产环境的配置文件 同理 spring.profiles.active=test就是使用测试环境的配置文件
规则 默认配置与环境配置同时生效,同名配置项,profile配置优先
 
而激活指定环境,要么在配置文件中写spring.profiles.active=xxx 要么使用命令行,使用命令行时 java -jar xxxx.jar –spring.profile.active=xxx 这时也可以指定配置文件命令行甚至能修改配置文件的任意值,命令行优先 
@Profile条件装配功能 比如一个配置类,在方法上注明@Profile(“xxx”),就代表该环境下这个方法才生效
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.example.boot.config;import  com.example.boot.bean.Color;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;import  org.springframework.context.annotation.Profile;@Configuration public  class  MyConfig  {    @Profile("prod")      @Bean      public  Color red ()  {         return  new  Color ();     }     @Profile("test")      @Bean      public  Color green ()  {         return  new  Color ();     } } 
 
可以标注在类上,比如这个Boss类,就是测试环境下生效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package  com.example.boot.bean;import  lombok.Data;import  org.springframework.boot.context.properties.ConfigurationProperties;import  org.springframework.context.annotation.Profile;import  org.springframework.stereotype.Component;@Profile("test")   @Component @Data @ConfigurationProperties("person") public  class  Boss  implements  Person {    private  String name;     private  Integer age; } 
 
这个worker就是生产环境下才生效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package  com.example.boot.bean;import  lombok.Data;import  org.springframework.boot.context.properties.ConfigurationProperties;import  org.springframework.context.annotation.Profile;import  org.springframework.stereotype.Component;@Profile("prod")   @Data @ConfigurationProperties("person") @Component public  class  Worker  implements  Person  {    private  String name;     private  Integer age; } 
 
Profile分组功能 暂时用不到
外部化配置 也是暂时用不大的内容
这几天阳了 不在状态,原本早该结束了 害
自定义starter 一个场景启动器,里面是不含源码的,里面只是标识这个启动器有哪些依赖 
笑死 问题是这个笔记是真不多知道怎么该怎么记
把视频链接放着吧自定义starter细节 
SpringBoot启动原理 
创建SpringApplication
保存一些信息 
判定当前应用的类型,ClassUtils,Servlet 
bootstrappers 初始启动引导器(List <Bootstrpper> )去spring.factories文件中找
org.springframework.boot,Bootstrapper 
 
 
找ApplicationContextInitializer;也是去spring.factories找 ApplicationContextInitialize
List<ApplicationContextInitialize<?>> initializers 
 
 
找ApplicationListener;应用监听器,去spring.factories找ApplicationListener
List<ApplicationListener<?>> listeners 
 
 
 
 
运行SpringApplicaiton
StopWatch(新版没有这个了,是直接计时的) 
记录应用的启动时间 
创建引导上下文(Context环境) createBootstrapContext()
获得到所有之前的bootstrappers挨个执行initialize()来完成对引导启动器上下文环境设置 
 
 
让当前应用进入headless模式,java.awt.headless 
获取所有的RunListener(运行监听器) ,为了方便所有Listener进行事件绑定
getSpringFactoriesInstances去spring.factories找 SpringApplicationRunListener .class 
遍历SpringApplicationRunListener调用starting方法 
相当于通知所有感兴趣系统正在启动过程的人,项目正在starting 
 
 
事到如今,我终于知道args是什么了,原来是命令行传入的参数 
保存命令行参数:ApplicationArguments 
准备环境prepareEnvironment()
返回或者创建基础信息StandardServletEnvironment 
配置环境信息对象
 
绑定环境信息 
监听器调用environmentPrepared,通知所有的监听器当前环境主备完成 
 
 
创建IOC容器(createApplicationContext())
根据项目类型(servlet)创建容器 
当前会创建AnnotationConfigServletWebServerApplicationContext() 
 
 
准备ApplicationContext IOC容器的基本信息 prepareContext()
保存环境信息 
IOC容器的后置处理流程 
应用初始化器applyInitializers()
遍历所有的ApplicationContextInitializer,调用initialize,来对ioc容器进行初始化扩展功能 
遍历所有的Listener调用contextPrepared,EvenPublishRunListener 通知所有的监听器 contextPrepared 
 
 
所有的监听器调用contextLoaded 通知所有的监听器 contextPrepared 
 
 
刷新IOC容器 refreshContext
 
容器刷新完成后afterRefresh 
所有监听器调用listeners.started(context) 通知所有的监听器started 
调用所有的runners callRunners()
获取容器中的ApplicationRunner 
获取容器中的CommandLineRunner 
合并所有runner并且按照@Order进行排队 
遍历所有的runner,调用run方法 
 
 
如果以上有异常
 
调用所有监听器的running方法 listeners.running(context)   通知所有的监听器 running 
running如果有问题,继续通知failed,调用Listener的failed;通知所有的监听器failed 
 
 
 
好了 结束了
但没完全结束
纸上得来终觉浅,绝知此事要躬行  2022年12月30日 11点57分