Spring Boot详解

SpringBoot基本应用:
约定优于配置:

上面是引自官网的一段话,大概是说: Spring Boot 是所有基于 Spring 开发的项目的起点
Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件
约定优于配置(Convention over Configuration),又称按约定编程,是一种软件设计范式
本质上是说,系统、类库或框架应该假定合理的默认值(之所以是假定,是因为可以不一样),而非要求提供不必要的配置
比如说模型中有 一个名为User的类,那么数据库中对应的表一般就会默认命名为user
虽然可以不同,如对mybatis中只是字段值操作
只有在偏离这一个约定的时候,例如 想要将该表命名为person,才需要写有关这个名字的配置
当然并不是一定要写(如mybatis的类)
比如平时架构师搭建项目就是限制软件开发随便写代码,制定出一套规范,让开发人员按统一的要求进 行开发编码测试之类的
这样就加强了开发效率与审查代码效率
所以说写代码的时候就需要按要求命 名,这样统一规范的代码就有良好的可读性与维护性了
约定优于配置简单来理解,就是遵循约定
SpringBoot概念:
Spring优缺点分析:
优点:
Spring是Java企业版(Java Enterprise Edition, JEE,也称J2EE)的轻量级代替品,无需开发重量级的
Enterprise Java Bean(EJB), Spring为企业级Java开发提供了一种相对简单的方法
通过依赖注入和 面向切面编程,用简单的Java对象(Plain Old Java Object, POJO)实现了EJB的功能
缺点:
虽然Spring的组件代码是轻量级的,但它的配置却是重量级的
一开始, Spring用XML配置,而且是很 多XML配 置
Spring 2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML配置
Spring 3.0引入 了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML
所有这些配置都代表了开发时的损耗,因为在思考Spring特性配置和解决业务问题之间需要进行思维切 换
所以编写配置挤占了编写应用程序逻辑的时间,和所有框架一样, Spring实用,但与此同时它要求 的回报也不少
除此之外,项目的依赖管理也是一件耗时耗力的事情,在环境搭建时,需要分析要导入哪些库的坐标
而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本
随之而来的不兼容问题 就会严重阻碍项目的开发进度
SSM整合: Spring、 Spring MVC、 Mybatis、 Spring-Mybatis整合包、数据库驱动,引入依赖的数量繁多、容易存在版本冲突
Spring Boot解决上述spring问题:
SpringBoot对上述Spring的缺点进行的改善和优化,基于约定优于配置的思想
可以让开发人员不必在 配置与逻辑 业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中
从而大大提高了开发的 效率,一定程度上缩短 了项目周期
起步依赖 :
起步依赖本质上是一个Maven项目对象模型(Project Object Model, POM),定义了对其他库的传递依 赖
这些东西加在一起即支持某项功能
简单的说,起步依赖就是将具备某种功能的依赖坐标打包到一起,并提供一些默认的功能
自动配置:
springboot的自动配置,指的是springboot会自动将一些配置类的bean注册进ioc容器
我们可以需 要的地方使用@autowired或者@resource等注解来使用它
"自动"的表现形式就是我们只需要引我们想用功能的包,相关的配置我们完全不用管
springboot会自 动注入这些配置bean,我们直接使用这些bean即可
springboot:简单、快速、方便地搭建项目
对主流开发框架的无配置集成,极大提高了开发、部署效率
Spring Boot入门案例 :
spring的官网:https://spring.io
要学习新的技术,最好是去官网里进行学习
案例需求:请求Controller中的方法,并将返回值响应到页面
依赖管理:
<?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.lagou</groupId><artifactId>springbootdemo1</artifactId><version>1.0-SNAPSHOT</version><!--所有的springboot项目,都会直接或者间接的继承spring-boot-starter-parent1:指定项目编码格式为UTF-82:指定jdk版本为1.8(也就是8,只是叫做1.8)3:对项目依赖的版本进行管理,当前项目再引入其他常用的依赖时就不需要再指定版本号,避免版本冲突的问题4:默认的资源过滤和插件管理--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.2</version></parent><dependencies><!--引入了有关Spring Web及Spring MVC相关的依赖(它里面包含的,一般是依赖传递)--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!--由于有上面的spring-boot-starter-parent依赖,使得不需要指定版本号了后面的依赖或者新加的依赖,在这个项目中,我们也不需要指定版本号spring-boot-starter-parent依赖会进行管理--></dependency></dependencies><!--
可以将project打包为一个可以执行的jar
一般的jar包不会包含对应war包的配置,即前端页面,也就基本不能进行访问,即操作不了请求(一般说前端请求)
一般需要配置前端web.xml才可,实际上jar包是可以操作前端访问的请求的,如这里,百度上可以找到WebJars
实际上springboot就是操作WebJars的,jar包的启动操作前端请求
而由于war包是根据服务器来进行启动的,那么jar包也需要启动,但都是类
所以根据这里说,打包成一个可以执行的jar,那么说明,使得jar包也可以进行操作前端请求
但是既然是要操作jar包,一般需要跟war包一样的进行启动,也就是运行,一般有设置自动的启动以及手动的启动
但无论是自动的还是手动的都需要一个类,即我们称为启动类(后面有说明)
在war包下也一般是这样的说明,只是他使用的是在服务器里的启动类
所有这里的jar包就如79章博客的dubbo的那个jar包的启动(自动的),但这里我们操作手动的启动
一般打包后是自动执行启动,开发时需要手动执行启动
--><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
启动类操作:

对应的HelloController:
package com.lagou.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/****/
@RestController
@RequestMapping("hello")
public class HelloController {@RequestMapping("/boot")public String helloBoot() {return "Hello Spring Boot";}}
真正的启动类:
package com.lagou;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/**** SpringBoot的启动类,通常放在二级包中,比如这里的:com.lagou下* (但必须在某个包里面,而不能直接的在资源文件的下层目录,否则启动报错,这是规定,底层的原因,为什么会这样,你可以试着将一个类放在java资源文件夹下,然后在一个包里,来导入该类,你会发现,不能导入,为什么:因为他没有包指定,那么就应该直接的import 类名,但是他的意思也包括在当前包下导入,所以冲突了,一般来说,以当前包为主,所以你是导入不了该类的,即这里虽然说是规定,但又何尝不是因为导入不了包而形成的错误呢,可能某些操作会导入该包吧,比如,通过注解得到包名称,创建一个文件,里面加上该导入,然后通过java操作命令行执行,因为java本身好像操作这样的是执行不了的,可以使用网上的某些工具类,当然这只是想象中而已,可能实际上是某些判断,导致的)* 因为SpringBoot在做包扫描时,会扫描启动类所在的包,及其子包下的所有内容* 实际情况:一般我们会在com.lagou下创建对应的dao包,service包等等,如果启动类不在com.lagou下* 而在dao包里面,那么service也就扫描不到了* 所以这时可能会出现问题(如扫描的注解不进行操作,使得对应的环境没有)*///@SpringBootApplication该注解标识当前类为SpringBoot的启动类
//这时,springboot扫描项目时(先进行扫描,后进行启动,这个扫描只找@SpringBootApplication该注解)
//找到该注解,并可以得到对应的包路径
//就会扫描其包及其子包下的其他内容(这个扫描是扫描spring注解的)
@SpringBootApplication
public class SpringBootDome1Application {//由于这个是启动类,那么这个main方法,就会进行启动,实际上就是启动这个类,使得启动main方法的//就如命令启动一样,使用java 类名启动时,会执行main方法,所有可以说成手动或者自动的执行启动命令//一般打包后就是自动//自动的是找到该@SpringBootApplication注解,执行该类,然后启动springboot,然后他在找这个注解//手动少了前面的执行该类以及找注解public static void main(String[] args) {//样板代码//你只是定义注解并可以操作找到启动类的路径,但是springboot并没有真正的执行//也就是说,该注解并没有起作用//需要使用这个方法才可,并传入启动类的字节码文件,及其对应main方法的参数//执行后springboot会找到帮助@SpringBootApplication注解//并根据该注解进行扫描他的包及其子包//那么springboot就会使得帮我们准备所有的环境,包括server,监听器,装配spring的上下文等等SpringApplication.run(SpringBootDome1Application.class,args);}
}
至此,我们手动执行main方法,执行后,访问http://localhost:8080/hello/boot,发现返回了数据,那么操作成功
我们可以看到,若是以前,我们需要web.xml中的前端控制器,而这里他帮我们配置好了
因为无论是web.xml的操作还是这里,总体来说都是操作类
只是以前的jar包操作不了web.xml而已(因为他在web项目对应的文件夹下),而只有资源文件夹下的才可以操作
其他的基本直接忽略,如java文件夹下,若有xml文件,直接忽略,即其他的基本只能存放java类(资源文件夹下除外)
即springboot的确帮我们进行了配置,即操作了自动配置
接下来在知道他自动配置的情况下,若没扫描是如何:

将包路径改变,使得扫描不了其他spring注解,再次启动,发现,访问时,没有对应的网页

所以,虽然他帮我们进行了配置,但也要有扫描,否则对应的注解是不会起作用的,自然也找不到网页
所有至此,他的自动配置和扫描都操作完毕,且也确实如此
由原来的web.xml以及其他spring配置文件的操作,实现的类的操作,都由springboot来进行操作,使得对应类也操作
从而与war包启动一样,jar的启动也可以操作前端请求了
当然,由于启动类操作的是8080端口,那么再次启动其他启动类时或者本身(不同包也是如此)
基本都会提示关闭之前的启动类或者启动失败,虽然一样的类名会自动在后面加上别名,如"(1)",从1开始,然后是2,以此类推
一般后启动的相同名称则会加上对应的别名,具体看右上角的运行名称
如果我们在启动后,且访问了,再次将他关闭会怎么样:
会访问失败,虽然他自动配置了,但是却需要服务器来操作我们的访问,就如war包一样,虽然有配置,但需要服务器来操作请求
即这里springboot可以看成他本身也是操作服务器的
即他虽然自动进行配置且扫描,但对应的项目是在他身上运行的(服务器)
若关闭他,那么相当于会关闭服务器,实际上也会使得全部都没有了,即访问失败
至此,入门的第一个案例操作成功
SpringBoot 快速构建:
案例需求:请求Controller中的方法,并将返回值响应到页面
使用Spring Initializr方式构建Spring Boot项目 :
本质上说, Spring Initializr是一个Web应用,它提供了一个基本的项目结构,能够帮助我们快速构 建一个基础的Spring Boot项目

新的版本的idea一般会显示如下:

对应的地址直接写在了最上面
Project SDK(上面就是Module SDK)用于设置创建项目使用的JDK版本,这里,使用之前初始化设置好的JDK版本即可
在Choose Initializr Service URL(选择初始化服务地址)下使用默认的初始化服务地址"https://start.spring.io",简称默认地址
进行Spring Boot项目创建(注意使用快速方式创建Spring Boot项目时,所在主机须在联网状态下)
当然下面的是老版本的idea,但总体的流程都是差不多的

以前2.2.2是最新的,现在可能因为不同的idea或者默认地址而形成的不同的显示,一般是默认地址造成的,注意即可

Spring Boot项目就创建好了,创建好的Spring Boot项目结构如图
有指定对应的依赖,一般会使得部分变化,如上面指定web,那么在资源文件夹里面一般会出现static和templates文件夹,其他的基本固定:

使用Spring Initializr方式构建的Spring Boot项目会默认生成项目启动类,存放前端静态资源和页面的文件夹
编写项目配置的配置文件以及进行项目单元测试的测试类
实际上我们只需要保留src里面的main和test包(包括两者里面的内容)即可
其他的基本都可以删除,但也要注意,一般情况下会出现iml文件,最好不要删除(父子项目一般可以删除)
否则一般需要重新进入,等待一会即可
当然重新进入的话,他一般是没有的,刷新maven就会有了,所以他大概是maven的操作,一般创建boot项目会有他,因为maven
子项目(模块)一般不会出现
比如我的:

创建一个用于Web访问的Controller :
com.lagou包下创建名称为controller的包,在该包下创建一个请求处理控制类HelloController
并编写一个请求处理方法 (注意:将项目启动类SpringBootDemoApplication移动到com.lagou包下)
package com.lagou.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/****/
@RestController
@RequestMapping("hello")
public class HelloController {@RequestMapping("/boot")public String helloBoot() {return "Hello Spring Boot";}}
再次启动对应的启动类,访问,若要数据返回,则操作成功
实际上他默认的是8080端口,但我们可以进行修改,找到配置文件application.properties:
添加如下内容:
server.port=8888
再次启动,查看启动日志,发现,端口改变了(springboot操作的服务器端口改变)
访问http://localhost:8888/hello/boot,若有数据,则的确改变了
实际上就算使用对应的插件来打包操作web项目也是需要对应类的操作,即需要web.xml,那么也要上war包才可
而springboot则只需要jar包就可以了,这里他启动时并没有打包到maven里面
因为我们是直接的执行(target包下有对应的class,及其资源文件)
单元测试与热部署 :
单元测试 :
开发中,每当完成一个功能接口或业务方法的编写后,通常都会借助单元测试验证该功能是否正确
Spring Boot对项目的单元测试提供了很好的支持
在使用时,需要提前在项目的pom.xml文件中添加spring-boot-starter-test测试依赖启动器
快速构建springboot项目一般会加上该依赖
即使用Spring Initializr方式搭建的Spring Boot项目,会自动加入spring-boot-starter-test测试依赖启动器,无需再手动添加
然后可以通过相关注解实现单元测试
演示:
添加spring-boot-starter-test测试依赖启动器:
在项目的pom.xml文件中添加spring-boot-starter-test测试依赖启动器,示例代码如下 :
 <dependency><!--使得可以进行测试spring boot,这个@SpringBootTest需要这个依赖--><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><!--一般需要他来操作测试,如@RunWith(JUnit4.class)--><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency>
对应测试的启动类Springbootdome2ApplicationTests:
package com.lagou;import com.lagou.controller.HelloController;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;//RunWith:运行器,参数一般指定的是测试环境,在什么项目下
//一般需要指定对应项目的运行环境,环境对应的操作不匹配,一般会报错
//当然,spring boot操作时,基本什么运行环境都可以
//即@RunWith(JUnit4.class)和@RunWith(SpringJUnit4ClassRunner.class)和@RunWith(SpringRunner.class)
//这三个都可以,其中,不写默认是@RunWith(JUnit4.class),当然,若没有对应的junit依赖,那么就是其他依赖的默认,如这里的junit-jupiter依赖
//若都没有,那肯定报错啊(没有类嘛)
//具体是谁优先,百度查看即可,但junit一般是最优先的
//而spring操作时,只能是spring运行环境
//即@RunWith(SpringJUnit4ClassRunner.class)和@RunWith(SpringRunner.class)可以
//但@RunWith(JUnit4.class)不可以(不写,默认是他或者其他的依赖的默认)
//大概是因为spring需要读取配置类或者配置文件等等,可能需要操作他们
//除非你不需要对应的容器了,因为当你加上后,对应的注入是没有操作注入的并没有实例
//对应注入的值也就是null,因为注入没有的,会报错
//在操作@RunWith(JUnit4.class)时,一般默认的是JUnit4.class
//即可以不写@RunWith(JUnit4.class),虽然写了也没有问题
//若操作junit,则是junit环境
//@RunWith(JUnit4.class)
//一般spring操作时需要SpringJUnit4ClassRunner.class,因为spring整合junit的依赖,即spring-test依赖
//这个依赖spring-boot-starter-test里面有,所有直接写上即可
//@RunWith(SpringJUnit4ClassRunner.class)
//若要使用springboot项目的测试,一般需要springboot的测试环境,也在对应的spring-test依赖里面
//所有直接写即可,实际上这个只需要spring-test依赖即可,spring-boot-starter-test依赖可以不要,只是下面的需要
@RunWith(SpringRunner.class)//注意说明的是SpringRunner.class需要spring-test依赖,而不是@RunWith
//而@RunWith需要junit依赖
//标记当前类为SpringBoot测试类,会加载得到项目的ApplicationContext上下文环境(相当于会执行启动类)
@SpringBootTest //这个需要对应的spring-boot-starter-test依赖,上面的只需要spring-test依赖即可
//所以都需要使得可以测试,而正好spring-boot-starter-test依赖包含spring-test依赖
//所以只需要spring-boot-starter-test依赖即可
class Springbootdome2ApplicationTests {/*需求,调用HelloController里面的helloBoot方法*///springboot帮我们扫描了@Autowiredprivate HelloController helloController;@Test //注意,这个@Test不是原来的import org.junit.Test(一般来说boot的低版本会自带这个,即import org.junit.Test,比如2.1.6.RELEASE,但不会有import org.junit.jupiter.api.Test,即只有import org.junit.Test,而高版本不会有import org.junit.Test,一般只有import org.junit.jupiter.api.Test,比如2.4.2,这里是没加上.RELEASE的,那么一般就是2.4.2,自己测试就知道了)//而是import org.junit.jupiter.api.Test//当然,操作原来的也可以,但需要类是public才可,否则不能执行方法//可能import org.junit.Test需要public的这个访问权限的原因//而import org.junit.jupiter.api.Test不需要(但是若是public,那么一般也可以,基本不会报错)//注意一下即可,这里就使用这个//类基本只能是默认的或者public权限public void contextLoads() {String s = helloController.helloBoot();System.out.println(s); //Hello Spring Boot}//上面的操作,可以想象成一个springmvc的项目,我们扫描后,进行注入,然后操作,只是这里并没有指定配置文件//因为@SpringBootTest操作了对应的扫描的结果//一般项目启动时对应的扫描的结果一般放在全局的servletContext里面,使得被操作//即ApplicationContext对象被操作,但这里他正好直接加载,而不从全局取出,实际上该对象就是对应的如类似于/*ApplicationContext classPathXmlApplicationContext = newClassPathXmlApplicationContext("applicationContext.xml");*///得到classPathXmlApplicationContext的值,即ApplicationContext对象//一般只要操作对应的配置即可,无论web项目还是springboot项目}
启动后,执行方法,若返回正确的数据,则操作成功
注意:测试的类也要在启动类对应的包或者包下,即对应的,否则是操作不了,即会报错(初始化报错)
所以一般我们快速的创建Spring Boot项目时,对应的测试类的目录一般与启动类一样,这就是原因
热部署:
在开发过程中,通常会对一段业务代码不断地修改测试,在修改之后往往需要重启服务,有些服务需要加载很久才能启动成功
这种不必要的重复操作极大的降低了程序开发效率
为此, Spring Boot框架专门提供了进行热部署的依赖启动器,用于进行项目热部署
而无需手动重启项目(也就是重新执行启动类,或者说重新执行启动类的main方法)
简单来说,热部署就是:在修改完代码之后(无论是配置文件还是类,基本只要是项目的进行了修改就会更新)
不需要重新启动容器,就可以实现更新,但是需要等待他更新
等日志出现,那么才会真正的部署,中途再次改变,会影响日志的出现
一般会迟一点,因为有缓冲等待(大概等几秒才更新),防止你频繁的更新(每次的改变,基本会重置该等待)
使用步骤:
1:添加SpringBoot的热部署依赖启动器
2:开启Idea的自动编译
3:开启Idea的在项目运行中自动编译的功能
添加spring-boot-devtools热部署依赖启动器:
在Spring Boot项目进行热部署测试之前,需要先在项目的pom.xml文件中添加spring-boot-devtools热部署依赖启动器:
    <!--引入热部署依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId></dependency>
由于使用的是IDEA开发工具,添加热部署依赖后可能没有任何效果,接下来还需要针对IDEA开发工具进行热部署相关的功能设置
IDEA工具热部署设置:
选择IDEA工具界面的【File】 ->【Settings】选项,打开Compiler面板设置页面

选择Build下的Compiler选项,在右侧勾选"Build project automatically"选项将项目设置为自动编译
但有时候我们的启动一般都会去编译,所以他一般用在热部署
单击【Apply】→【OK】按钮保存设置
在项目任意页面中使用组合快捷键"Ctrl+Shift+Alt+/"打开Maintenance选项框,选中并打开Registry页面:

列表中找到"compiler.automake.allow.when.app.running",将该选项后的Value值勾选
用于指定IDEA工具在程序运行过程中自动编译,最后单击【Close】按钮完成设置
注意:在高版本的idea,可能找不到这个选项,那是因为对应的选项不在这里,在如下:

点击这个使得变成勾勾即可
总体来说,就是打开自动编译,并使得可以在项目运行时自动编译,而对应的依赖使得进行更新(修改的部分)
热部署效果测试:
执行http://localhost:8888/hello/boot,一般页面返回是

修改对应的类:
页面原始输出的内容是"Hello Spring Boot"
为了测试配置的热部署是否有效,接下来,在不关闭当前项目的情况下
将HelloController类中的请求处理方法helloBoot()进行改变,如下:
package com.lagou.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/****/
@RestController
@RequestMapping("hello")
public class HelloController {@RequestMapping("/boot")public String helloBoot() {return "Hello Spring Boot 哈哈哈"; //修改成这个}}
查看控制台信息会发现项目能够自动构建和编译(一般需要等待一会,就会出现对应的日志),说明项目热部署生效
接着访问如下:

可以看出,浏览器输出了"Hello Spring Boot 哈哈哈",说明项目热部署配置成功
可以试着将上面三个步骤随便取消一个,一般都不会进行更新,所以这三个步骤必须要,才基本可以热部署
实际上热部署就是自动的部署,因为我们需要改变内存里的内容,一般我们都是手动的重新部署的
实际上对于浏览器来说可能会有缓存,为了避免浏览器的缓存
我们会使用ctrl+f5进行强制刷新(不使用缓存的刷新,笔记本一般需要加上fn,且对应的浏览器有这个功能才可)
全局配置文件 :
全局配置文件能够对一些默认配置值进行修改
Spring Boot使用一个application.properties或者application.yaml的文件作为全局配置文件
该文件存放在src/main/resource目录或者类路径的/config,一般会选择resource目录
接下来,将针对这两种全局配置文件进行讲解 :
Spring Boot配置文件的命名及其格式:
application.properties,application.yaml,application.yml(前面一个的简写,相当于是一样的)
application.properties配置文件 :
使用Spring Initializr方式构建Spring Boot项目时,会在resource目录下自动生成一个空的application.properties文件
Spring Boot项目启动时会自动加载application.properties文件
我们可以在application.properties文件中定义Spring Boot项目的相关属性
当然,这些相关属性可以是系统属性、环境变量、命令参数等等信息,也可以是自定义配置文件名称和位置
比如:
#修改tomcat的端口号(有些时候也可以说是版本号)
server.port=8888
#定义数据库的连接信息  JdbcTemplate
#mysql-connector-java中5的版本就不加cj,6的版本需要加(高的版本一般也要加)
#这里是6.0.6的版本,所以需要加,因为版本不同,类结构可能也是不同的,不加的话
#可能会报错,但一般是可以兼容的,但也只是对高版本来说,即高版本可以加cj,也可以不加
#但低版本不能加cj,否则一般启动时会报错(初始化报错)
#启动一般说明的是:启动启动类报错(直接停止,需要重新启动了)
#若你是高版本,那么可以不用改变,但为了以防万一,最好修改)
#由于这是全局配置,spring boot在操作对应的数据库信息时
#自动配置的依赖操作数据库信息,比如jdbc依赖和mybatis依赖,只要是需要数据库信息的,就有这里的解释
#即会使用下面这些参数当成对应数据库信息的参数
#然后自动配置,若没有,则一般使用默认的值(只有用户名和密码有默认值,前面两个必须写,否则一般会报错)
#这个报错会使得访问不了,即后面的springmvc的操作停止,相当于没有自动配置了#注意:
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#后面不能有空格,否则报错,这是一个特殊的地方,当然可以不写,会根据驱动默认加上的
#其他的基本都可以有空格(且空格无影响,相当于没有什么,基本不会参与到数值里面去)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/lagou
spring.datasource.username=root
spring.datasource.password=123456
操作了对应的数据库,那么一般需要对应的驱动包
spring boot的依赖中并不是所有的包都有传递,有些需要自己加上,如这个驱动包:
 <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>6.0.6</version> <!--
如果自己写版本,则使用自己的,但是因为spring boot的版本一般是经过测试的,也就基本没有版本冲突
自己写的版本,可能会出现冲突,所以也最好不要自己写(除非你确定不会发生冲突)
--></dependency>
还需要如下的包:
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><version>2.7.1</version><!--
虽然spring boot一般是进行测试的,但也有可能对应的包突然不存在(不开放了),所以这时需要指定版本
虽然基本不会,但这里使用我写的版本,实际上使用默认的也可
--></dependency>
<!--一般有对应的spring-jdbc包,使用连接池的,可以被注入-->
至此我们可以操作如下:
package com.lagou.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/****/
@RestController
@RequestMapping("hello")
public class HelloController {@RequestMapping("/boot")public String helloBoot() {return "Hello Spring Boot 哈哈哈";}@Autowiredprivate JdbcTemplate jdbcTemplate; //可能会报红,但这时idea检查的操作,运行时不会出错的@RequestMapping("/jdbc")public String jdbc(){return jdbcTemplate.toString();//org.springframework.jdbc.core.JdbcTemplate@239a95aa(我这里是,不同的执行结果一般是不同的)//就如创建对象的地址,再次创建时,对应的地址值基本也是不一样的//除了自带的,如String,如相同取一个地址//还有对应规定的,如数组,如基本第一(几)个类型的数组地址是固定}
}
进行访问,若有对应的数据,那么注入完成,即操作完成
接下来,通过一个案例对Spring Boot项目中application.properties配置文件的具体使用进行讲解
演示:
预先准备了两个实体类文件,后续会演示将application.properties配置文件中的自定义配置属性注入到Person实体类的对应属性中
先在项目(快速构建的那个项目)的com.lagou包下创建一个pojo包,并在该包下创建两个实体类Pet和Person:
package com.lagou.pojo;/*** 宠物类*/
public class Pet {private String type; //品种private String name; //名称@Overridepublic String toString() {return "Pet{" +"type='" + type + '\'' +", name='" + name + '\'' +'}';}public String getType() {return type;}public void setType(String type) {this.type = type;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
package com.lagou.pojo;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.List;
import java.util.Map;/****/
@Component //记得要被spring boot扫描到
//将配置文件中所有以person开头的配置信息注入到当前类中
//前提1:必须保证配置文件中person.xxx的xxx要与Person类的setxxx中的xxx一致(首字母可以忽略大小写)
//前提2:必须保证当前Person中的属性都具有set方法,因为是使用setxxx方法进行注入的
//若没有,如没有满足对应的存在(首字母可以忽略大小写,则代表没有,那么会报错)
@ConfigurationProperties(prefix = "person")
public class Person {private int id; //idprivate String name; //名称private List hobby; //爱好private String[] family; //家庭成员private Map map;private Pet pet; //宠物/*当然有类似于这样的如下:@Value("${person.id}")private int id; //id@Value("${person.name}")private String name; //名称@Value("${person.hobby}")private List hobby; //爱好@Value("${person.family}")private String[] family; //家庭成员private Map map;private Pet pet; //宠物但是一般却不能直接操作对应的map集合和类,因为这里只给出了参数(因为参数只能是一个)具体的解决方案可以百度,一般来说会使得配置文件里面参数进行包括起来,只包含一个参数*/@Overridepublic String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +", hobby=" + hobby +", family=" + Arrays.toString(family) +", map=" + map +", pet=" + pet +'}';}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public List getHobby() {return hobby;}public void setHobby(List hobby) {this.hobby = hobby;}public String[] getFamily() {return family;}public void setFamily(String[] family) {this.family = family;}public Map getMap() {return map;}public void setMap(Map map) {this.map = map;}public Pet getPet() {return pet;}public void setPet(Pet pet) {this.pet = pet;}
}
@ConfigurationProperties(prefix = “person”)注解的作用:
将配置文件中以person开头的属性值通过setXX()方法注入到实体类对应属性中
@Component注解的作用是将当前注入属性值的Person类对象作为Bean组件放到Spring容器中
只有这样才能被@ConfigurationProperties注解进行赋值,就如@Value一样需要对应的实例
但是若@ConfigurationProperties注解和@Value共存,那么@ConfigurationProperties注解会覆盖@Value注解的操作
若@Value注解报错,那么启动基本就会报错,使得访问不了
打开项目的resources目录下的application.properties配置文件,在该配置文件中编写需要对Person类设置的配置属性
#自定义配置信息注入到Person对象中
person.id=100
person.name=哈哈
#list
person.hobby=喝水,吃早餐
#String[]
person.family=儿子,老婆
#集合和对象,一般都需要多一个层(多了.)
#Map
person.map.k1=v1
person.map.k2=v2
#Pet对象
person.pet.type=狗
person.pet.name=旺财
#他们可以使用@Value注解来进行注入(对应的参数获取),因为他们是全局的
查看application.properties配置文件是否正确,同时查看属性配置效果
打开通过IDEA工具创建的项目测试类,在该测试类中引入Person实体类Bean,并进行输出测试
 @Autowiredprivate Person person;@Testpublic void configurationTest(){System.out.println(person);/*Person{id=100, name='哈哈', hobby=[喝水,吃早餐], family=[儿子,老婆], map={k1=v1, k2=v2}, pet=Pet{type='狗', name='旺财'}}*/}
若返回数据则代表注入成功,即操作成功
但是这里可能会出现乱码问题,因为properties对应操作特殊字符时(如中文)时
不会使用设置的编码,而是使用ISO 8859-1来解码,这就导致了无论你怎么设置编码(在能操作中文的编码的情况下)
对应的基本都是乱码,因为我们编码的一般不可能是ISO 8859-1,因为要操作中文,他基本不能解析该中文
那么对应编码也是有问题的(就算是同样的该编码,可能也是乱码)
那么如何不让他进行默认的ISO 8859-1来解码呢,点击如下:

只要右边的勾勾,勾上了即可,代表操作我们的格式,因为对应的编码一般从全局得到,左边的一般是显示的文件编码
好像并不会参与程序里面的编码,只是用来看的,最好其他的配置都是UTF-8,以防万一

至此我们可以在对应的HelloController类里加上如下代码:
 @Autowiredprivate Person person;@RequestMapping("person")public String showPerson(){return person.toString();}
至此我们再次观看对应的返回数据,若有,则代表真正的操作成功
但是这时可能也有乱码,一般是tomcat和对应的http编码,可以加上如下代码:
#解决中文乱码
#server.tomcat.uri-encoding=UTF-8
#spring.http.encoding.force=true
#spring.http.encoding.charset=UTF-8
#spring.http.encoding.enabled=true
#上面spring boot版本高的一般不能操作,与如下的作用基本类似
#一般是如下
#解决中文乱码
#tomcat编码,得到的请求数据以下面的编码格式进行操作,一般浏览器可以操作中文,(所以可以不用设置)
#如操作请求过来的参数,但也有可能url出现乱码,一般是js的操作,到那时一般需要使用encodeURI来进行传输
server.tomcat.uri-encoding=UTF-8
#对请求或者响应的编码设置(使得数据被操作),一般是解决post的,操作过滤
#因为不操作过滤,那么已经变化了,再次设置编码就并没有作用了(如springmvc就需要过滤,而不是去方法里面设置编码)
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
application.yaml(yml)配置文件:
YAML文件格式是Spring Boot支持的一种JSON文件格式,相较于传统的Properties配置文件, YAML文件以数据为核心
是一种更为直观且容易被电脑识别的数据序列化格式
application.yaml配置文件的工作原理和application.properties是一样的,只不过yaml格式配置文件看起来更简洁一些
YAML文件的扩展名可以使用.yml或者.yaml
application.yml文件使用 "key:(空格) value"格式配置属性,使用缩进控制层级关系
SpringBoot的三种配置文件是可以共存的,也就是说,可以写这三个配置文件,都会进行读取:
<!--
点击spring-boot-starter-parent(使用ctrl+鼠标左键),往下找可以找到
当然修改这个里面的值,不会操作当前项目,因为他不是项目里面的
他只是一个投影而已(可以试着全部删除,然后启动运行,发现并没有影响)
-->
<includes><!--谁在前面,谁基本先读取,当然他可能并不绝对,也受版本影响(他这里可能还是不变,但顺序不同)--><include>**/application*.yml</include><include>**/application*.yaml</include><include>**/application*.properties</include></includes><!--
一般的读取顺序是,yaml,yml,properties(可能不同版本yml在yaml前面,现在通常是yml在前面了,比如上面的顺序,但properties基本在后面)
后读取的那么相关参数会进行覆盖,没有的不会
如properties设置端口为8888端口
那么前面两个无论怎么设置端口,都是8888端口了,当然若有其他的操作,自然不会覆盖,因为properties并没有设置
假设都没有对应的配置,那么使用默认值,如都没有设置端口,那么就是8080端口(默认值)
-->
这里,针对不同数据类型的属性值,介绍一下YAML
value值为普通数据类型(例如数字、字符串、布尔等)
当YAML配置文件中配置的属性值为普通数据类型时,可以直接配置对应的属性值
同时对于字符串类型的属性值,不需要额外添加引号,示例代码如下
server:
#记得":"后面需要空格,当你删除空格后,会发现属性的port颜色变了,即配置出现错误(一般情况下,只会认为是一个整体值,即是一个值,而基本不会当成属性看,所以如果不是特殊情况的赋值下,那么一般是不会报错的,特殊情况一般是:比如有定义了属性,确还赋值,且无论是否在前和在后,这样通常就会报错)
#比如:
#server: 66
#  port:
#    8081  所以,值并不是非要在一行,只需要是空格即可,所以可以换行,但是,如果加上了":",那么认为是属性了,前提是":"后面有空格,比如8081:(空格),而属性,就需要换行,否则会启动报错
#那么就认为是属性了
#  8888
#66和8888只要存在一个,都会报错(即无论是否在前还是在后)
#当有其他当前的直接级别(在级别的级别里面,并不会影响不是当前级别的其他级别),有正常配置时,会启动报错
#除非什么都不加上
#且层级关系要正确,如port与server同一级或者不能这样server: port: 8001,如果是server: port:8001,那么后面会看成一个值,所以没有问题(会默认为8080,相当于随意的设置properties的属性一样,而不会报错)
#那么读取时(同一级来说,在一行的上面说明了),会报错(因为该级别基本没有port,除非有,那么不会报错)
#如context-path,可以与server和servlet同一级,因为有该级别,且区分级别时,只要有空格即可,无论该空格是多少port: 8080  #设置起始路径,需要在项目前面加上hello才可,即访问http://localhost:8081/hello/hello/bootservlet:    #一般我们设置为/,大型的项目情况下可能会进行操作其他路径context-path: /hello
value值为数组和单列集合:
当YAML配置文件中配置的属性值为数组或单列集合类型时,主要有两种书写方式:缩进式写法和行内式写法
其中,缩进式写法还有两种表示形式,示例代码如下
person:   hobby:     - play    - read- sleep #好像也可以这样
person:   hobby:     - play    - read- sleep
#"-"可以与hobby同级,假设是这样
person:   hobby:     - play    - read- sleep
#那么返回是数据是[play - read, sleep],数组长度为2
#也就是说不是同一级的,当成一体,知道看到同一级的,但是却不能这样
person:   hobby:     - play    - read- sleep
#只能与第一个的级别相同或者小级别,否则报错
#若是这样
person:   hobby:     - play    - read- sleep
#那么返回,因为都是小级别[play - read - sleep],数组长度为1,与自己的小级别一体#上面对于大多数的配置都是如此,符合yml和yaml的语法,以层级关系代表从属
或者使用如下示例形式:
person:  hobby:    play,    read,    sleep
#后面的值,不能与hobby同一级,但他们之间的级别不做要求,即可以这样
person:  hobby:    play,    read,    sleep
上述代码中,在YAML配置文件中通过两种缩进式写法对person对象的单列集合(或数组)类型的爱好hobby
赋值为play、read和sleep,其中一种形式为"-(空格)属性值"
另一种形式为多个属性值之前加英文逗号分隔(注意,最后一个属性值后不要加逗号)
行内式写法:
person:   hobby: [play,read,sleep]
#也可以这样
person:   hobby: play,read,sleep
#这个相当于
person:  hobby:    play,    read,sleep
#至此我们发现之所以加上,(逗号),是为了进行分开,而之所以不在最后加上逗号,是为了防止出现空数据
#因为在最后加上逗号,那么默认该逗号后面有数据,如果不加,那么就是空的数据,相当于字符串里面的""
#但是若使用[]进行包括,最后的逗号写不写都一样,只要后面没有数据,那么该都会会自动的删除(只对[]来说)
#所以最后的逗号对于[]来说没有""数据,其中两个逗号之间不写,会报错(启动报错),而不是[]的会认为是空数据
通过上述示例对比发现,YAML配置文件的行内式写法更加简明、方便
另外,包含属性值的中括号"[]"还可以进一步省略,在进行属性赋值时,程序会自动匹配和校对
我们发现无论是什么样的操作":"后面都需要一个空格,这是规定,也是为了好观察
value值为Map集合和对象:
当YAML配置文件中配置的属性值为Map集合或对象类型时
YAML配置文件格式同样可以分为两种书写方式:缩进式写法和行内式写法
其中,缩进式写法的示例代码如下:
person:  map:     k1: v1    k2: v2
对应的行内式写法示例代码如下 :
person:  map: {k1: v1,k2: v2}
在YAML配置文件中,配置的属性值为Map集合或对象类型时,缩进式写法的形式按照YAML文件格式编写即可
而行内式写法的属性值要用大括号"{}"包含
接下来,在Properties配置文件演示案例基础上,通过配置application.yaml配置文件对Person对象进行赋值,具体使用如下
在项目的resources目录下,新建一个application.yaml配置文件,在该配置文件中编写为Person类设置的配置属性
#对实体类对象Person进行属性配置
person:  id: 1  name: 王二麻子  family:    - 妻    - 妾  hobby:    - play    - read    - sleep  map:    k1: value1    k2: value2  pet:    type: 狗    name: 哈士奇
#该设置若在properties里面,不会有任何的作用,但并不会报错,因为在properties里面,不会识别的那么就会跳过
#yaml和yml(前面的简写)的格式基本都是一致的,即都可以这样写
再次执行测试:
若返回数据,则操作成功
好像这时候更加的操作@Value()注解都只能操作单独的类型了(数组,集合,类好像都不能操作),你可以百度进行查找对应操作
大概是对应的@ConfigurationProperties(prefix = “person”)注解存在使得移除了吧
最后注意一下:使用spring boot时,对应的url访问不能出现多余的/,也就是说
//斜杠的访问不会被后端(以前说是浏览器的作用,实际上是因为浏览器访问后端造成的)解析成一个/
这时就会出现访问不了,或者网页不存在
配置文件属性值的注入:
使用Spring Boot全局配置文件设置属性时:
如果配置属性是Spring Boot已有属性,例如服务端口server.port
那么Spring Boot内部自动扫描并读取这些配置文件时,对应的属性值覆盖默认属性
如果配置的属性是用户自定义属性,例如刚刚自定义的Person实体类属性,则不会自动的覆盖,因为没有
那么他只是定义,并没有操作,需要我们在程序中手动注入这些配置属性方可操作,而不是自动的使用(因为定义)
那么实际上也可以这样的操作:如@Value(“${server.port}”),那么可以注入对应的端口值
因为虽然他覆盖了对应的默认属性,但他任然是定义的,既然是定义的,就可以使用
总体而言:该配置在spring boot中多了一个已有属性进行覆盖,其余的与普通的配置存放信息文件是一样的
需要被使用(如properties文件,以前有操作数据库的信息,那时就是被使用)
然后使用注解注入对应的实例,当然若没有对应的注入配置属性,那么对应的实例自然是使用默认的值的
当然他们注入的方式基本都是扫描时进行操作的,只有扫描时,对应的注解操作才会进行
而对应的配置文件信息(写的信息)实际上也是使用后的再扫描的(在spring中也有注解和配置文件操作他,他基本是全局的)
Spring Boot支持多种注入配置文件属性的方式,下面来介绍如何使用注解@ConfigurationProperties和@Value注入属性
使用@ConfigurationProperties注入属性:
Spring Boot提供的@ConfigurationProperties注解
用来快速、方便地将配置文件中的自定义属性值批量注入到某个Bean对象的多个对应属性中
前面的操作中,我们就使用了这个注解并说明了,所以这里就不作说明
实际上上面的注解方式不够灵活,要想要更加的灵活,一般使用如下方式进行注入属性值
使用@Value注入属性:
@Value注解是Spring框架提供的,用来读取配置文件中的属性值并逐个注入到Bean对象的对应属性中
Spring Boot框架从Spring框架中对@Value注解进行了默认继承
所以在Spring Boot框架中还可以使用该注解读取和注入配置文件属性值,使用@Value注入属性的示例代码如下
@Value("${person.id}")
private int id;
上述代码中,使用@Component和@Value注入Person实体类的id属性
其中,@Value不仅可以将配置文件的属性注入Person的id属性,还可以直接给id属性直接的赋值,如@Value(“1”),直接赋值为1
这点是@ConfigurationProperties不支持的,因为他只能去配置文件里加上对应的属性及其值才可
不够灵活,且使得配置文件信息变多
演示@Value注解读取并注入配置文件属性的使用:
在com.lagou.pojo包下新创建一个实体类Student,并使用@Value注解注入属性
package com.lagou.pojo;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/****/
@Component
public class Student {@Value("${person.id}")private int id;@Value("${person.name}")private String name; //名称@Overridepublic String toString() {return "Student{" +"id=" + id +", name='" + name + '\'' +'}';}
}
Student类使用@Value注解将配置文件的属性值读取和注入
从上述示例代码可以看出
使用@Value注解方式需要对每一个属性注入设置(相当于直接的赋值,反射可以赋值private,在其他地方)
同时又免去了属性的setXX()方法
再次打开测试类进行测试:
 @Autowiredprivate Student student;@Test public void studentTest() {System.out.println(student); //Student{id=1, name='王二麻子'}}
若返回数据,则操作成功
可以看出,测试方法studentTest()运行成功,同时正确打印出了Student实体类对象
需要说明的是,本示例中只是使用@Value注解对实例中Student对象的普通类型属性进行了赋值演示
而@Value注解对于properties配置文件的格式属性中包含了Map集合、对象的不支持@Value注入
而YAML(YML)文件格式的配置文件的属性list(其他集合基本也是)集合和数组,map集合,对象等注入都不支持
上面不支持的,如果赋值会出现错误
这就是他的缺点(以前好像可以)
自定义配置:
spring Boot免除了项目中大部分的手动配置,对于一些特定情况,我们可以通过修改全局配置文件以适应具体生产环境
可以说,几乎所有的配置都可以写在application.yml文件中
Spring Boot会自动加载全局配置文件从而免除我们手动加载的烦恼(因为设置好的三个)
但是,如果我们自定义配置文件,Spring Boot是无法识别这些配置文件的(因为只有那三个可以),此时就需要我们手动加载
接下来,将针对Spring Boot的自定义配置文件及其加载方式进行讲解
使用@PropertySource加载配置文件
对于这种加载自定义配置文件的需求,可以使用@PropertySource注解来实现
@PropertySource注解用于指定自定义配置文件的具体位置和名称
当然,如果需要将自定义配置文件中的属性值注入到对应类的属性中
可以使用@ConfigurationProperties或者@Value注解进行属性值注入
因为自定义配置文件与其他三个配置文件一样,都被读取操作了,自然结果是一样的
只是不会自动读取操作该自定义的配置文件而已,需要手动读取操作
演示:
打开Spring Boot项目的resources目录
在项目的类路径下新建一个test.properties自定义配置文件,在该配置文件中编写需要设置的配置属性
#对实体类对象MyProperties进行属性配置
test.id=110
test.name=test
在com.lagou.pojo包下新创建一个配置类MyProperties
提供test.properties自定义配置文件中对应的属性,并根据@PropertySource注解的使用进行相关配置
package com.lagou.pojo;/****/import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;@Component  // 自定义配置类
@PropertySource("classpath:test.properties")  // 指定自定义配置文件位置和名称,该文件的类型好像并不做要求
//好像只要对应的文件名称对应即可,到那时,一般是
//在扫描时,一般会先操作该@PropertySource注解,然后再操作其他的注解,使得可以操作属性值
@ConfigurationProperties(prefix = "test") // 指定配置文件注入属性前缀
public class MyProperties {private int id;private String name;@Overridepublic String toString() {return "MyProperties{" +"id=" + id +", name='" + name + '\'' +'}';}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
主要是一个自定义配置类,通过相关注解引入了自定义的配置文件,并完成了自定义属性值的注入
针对示例中的几个注解,具体说明如下
@PropertySource(“classpath:test.properties”)注解指定了自定义配置文件的位置和名称
此示例表示自定义配置文件为classpath类路径下的test.properties文件,也就是项目下的(打包后可以看到对应的classes)
@ConfigurationProperties(prefix = “test”)注解将上述自定义配置文件test.properties中以test开头的属性值注入到该配置类属性中
进行测试:
@Autowiredprivate MyProperties myProperties;@Testpublic void myPropertiesTest() {System.out.println(myProperties);//MyProperties{id=110, name='test'}}
若有对应的数据,则操作成功
注意:在properties里面,注释基本只能占一行(虽然也是一行一行的读取,那么其对应的不加注释也可)
否则要么是注释,要么当成值,要么没有注释,虽然与普通的文件类似,他都是一行一行的读取
其他的可以注释的自然省略,但是使用@PropertySource读取的文件基本都看成普通文件,那么是一行一行的读取
普通的文件基本没有注释一说,只要有一行是对应的属性值对应,那么就可以操作,比如:
对实体类对象MyProperties进行属性配置
test.id=110
test.name=test
这时无论他是什么类型的,都可以
使用@Configuration编写自定义配置类:
在Spring Boot框架中,推荐使用配置类的方式向容器中添加和配置组件
在Spring Boot框架中,通常使用@Configuration注解定义一个配置类
Spring Boot会自动扫描和识别配置类,从而替换传统Spring框架中的XML配置文件
当定义一个配置类后,还需要在类中的方法上使用@Bean注解进行组件配置,将方法的返回对象注入到Spring容器中
并且组件名称默认使用的是方法名,当然也可以使用@Bean注解的name或value属性自定义组件的名称
演示:
在项目下新建一个com.lagou.config包,并在该包下新创建一个类MyConfig,该类中不需要编写任何代码
而该类目前没有添加任何配置和注解,因此还无法正常被Spring Boot扫描和识别
创建了一个com.lagou.service包,里面创建空的MyService类,用来操作
接下来使用@Configuration注解将该MyConfig类声明一个配置类,内容如下:
package com.lagou.config;import com.lagou.service.MyService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/****/
@Configuration // 定义该类是一个配置类
public class MyConfig {@Bean    // 将返回值对象作为组件添加到Spring容器中,该组件id默认为方法名//当然也可以自己指定,如@Bean("myService2")public MyService myService(){return new MyService();}}
MyConfig是@Configuration注解声明的配置类(类似于声明了一个XML配置文件)
该配置类会被Spring Boot自动扫描识别,使用@Bean注解的myService()方法
其返回值对象会作为组件添加到了Spring容器中(类似于XML配置文件中的标签配置),并且该组件的id默认是方法名myService
测试类:
 @Autowiredprivate MyService myService;@Autowiredprivate MyConfig myConfig; //配置类也是会变成实例被使用的(加入对应的对象)@Testpublic void iocTest() {//返回结果,每次的运行一般都不会相同,因为对象(后面的就不在说明了)System.out.println(myService); //com.lagou.service.MyService@23564dd2System.out.println(myConfig);     //com.lagou.config.MyConfig$$EnhancerBySpringCGLIB$$d53e0fdf@54895681}
若返回数据,则代表注入成功,当然也可以操作如下:
@Autowired
//包记得要对应,import org.springframework.context.ApplicationContext;private ApplicationContext applicationContext;
//使用测试的注解,即测试的读取配置文件或者配置类(这里实际上也是),一般会将IOC容器本身放入自己的IOC容器中
//那么也就可以得到对应的ApplicationContext了(本身,即自己)@Testpublic void Test() {System.out.println(applicationContext.getBean("myService2"));System.out.println(applicationContext.containsBean("myService2")); //查看是否有该key/*com.lagou.service.MyService@200d1a3dtrue*/}
上述代码中,先通过@Autowired注解引入了Spring容器实例ApplicationContext
然后在测试方法Test()中测试查看该容器中是否包括id为myService2的组件(也就是实例),若有对应的数据,则操作成功
从测试结果可以看出,测试方法Test()运行成功,且返回了true
表示Spirng的IOC容器中也已经包含了id为myService2的实例对象组件
说明使用自定义配置类的形式完成了向Spring容器进行组件的添加和配置
SpringBoot原理深入及源码剖析:
在源码分析之前,最好结合自己调试的代码为主
传统的Spring框架实现一个Web服务,需要导入各种依赖JAR包,然后编写对应的XML配置文件等,相较而言
Spring Boot显得更加方便、快捷和高效
那么,Spring Boot究竟如何做到这些的呢:
接下来分别针对Spring Boot框架的依赖管理、自动配置通过源码进行深入分析
依赖管理:
问题1:为什么导入dependency时不需要指定版本:
在Spring Boot入门程序中,项目pom.xml文件有两个核心依赖
分别是spring-boot-starter-parent和spring-boot-starter-web,关于这两个依赖的相关介绍具体如下:
spring-boot-starter-parent依赖:
在项目中的pom.xml文件中找到spring-boot-starter-parent依赖,示例代码如下:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.2</version><relativePath/> <!-- lookup parent from repository --></parent>
点击spring-boot-starter-parent(ctrl+鼠标左键进入)

上述代码中,将spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理
并将项目版本号统一为2.7.2.RELEASE,该版本号根据实际开发需求是可以修改的
使用"Ctrl+鼠标左键"进入并查看spring-boot-starter-parent底层源文件
发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies,核心代码具体如下
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.7.2</version></parent>
继续查看spring-boot-dependencies底层源文件,核心代码具体如下:
<properties><activemq.version>5.16.5</activemq.version><antlr2.version>2.7.7</antlr2.version>...<!--后面还有很多,就不显示了--><!--
定义了依赖的版本,所以我们引入其他相关依赖时可以不指定版本号,因为spring-boot-dependencies帮我们定义好了
但也要记住,他是有限的,也就是说,大多数的帮我们定义好了版本,但有些没有,但基本不会出现
因为这些版本都是Spring官方经过兼容性测试的,基本不会出现版本冲突或者兼容性的问题那么你可能会有疑问,他只是定义,但应该需要我们自己去变成变量,那么为什么我们不用编写呢,实际上若我们不去编写
他会自动从父依赖里面操作规定的给外面的标签来对应(该依赖不能使用,所以一般需要我们指定)
所以你可以在这里看到,后面有对应的依赖使用了该标签,而我们的编写就是对应他的
实际上就是操作了锁定dependencyManagement
dependencyManagement中定义的只是依赖的声明,并不实现引入,你可以在后面看到他-->
从spring-boot-dependencies底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理
例如activemq、spring、tomcat等,都有与Spring Boot 2.7.2版本相匹配的版本
这也是pom.xml引入依赖文件不需要标注依赖文件版本号的原因
需要说明的是,如果pom.xml引入的依赖文件不是 spring-boot-starter-parent管理的
那么在pom.xml引入依赖文件时,需要使用标签指定依赖文件的版本号,这是肯定的,因为有限
问题2: spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的JAR包是从何而来的:
spring-boot-starter-web依赖:
查看spring-boot-starter-web依赖文件源码,核心代码具体如下:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.7.2</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId><version>2.7.2</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><version>2.7.2</version><scope>compile</scope></dependency><!--上面的我们不需要查看,下面的是否有点熟悉,发现正好是springmvc的依赖--><dependency><!--好像是操作监听的,但是一般springmvc的依赖会有这个依赖--><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.3.22</version><scope>compile</scope></dependency><dependency><!--有对应的类操作页面,如前端控制器,当然包括了spring-web--><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.22</version><scope>compile</scope></dependency></dependencies>
从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖
正是如此,在pom.xml中引入spring-boot-starter-web依赖启动器时,就可以实现Web场景开发
而不需要额外导入Tomcat服务器以及其他Web依赖文件等
当然,这些引入的依赖文件的版本号还是由spring-boot-starter-parent父依赖进行的统一管理
有哪些starter(有starter代表是依赖的集合,即多个依赖,且starter一般操作的是spring boot项目):
https://github.com/spring-projects/spring-boot/tree/v2.1.0.RELEASE/spring-boot-project/spring-boot-starters
https://mvnrepository.com/search?q=starter
上面的这些网站里面有对应的spring boot的依赖
Spring Boot除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖
我们可以打开Spring Boot官方文档,搜索"Starters"关键字查询场景依赖启动器
Spring Boot除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖
我们可以打开Spring Boot官方文档,搜索"Starters"关键字查询场景依赖启动器(也就是上面的第一个网站)
列出了Spring Boot官方提供的部分场景依赖启动器
这些依赖启动器适用于不同的场景开发,使用时只需要在pox.xml文件中导入对应的依赖启动器即可
需要说明的是,Spring Boot官方并不是针对所有场景开发的技术框架都提供了场景启动器
例如数据库操作框架MyBatis、阿里巴巴的Druid数据源等,Spring Boot官方就没有提供对应的依赖启动器
为了充分利用Spring Boot框架的优势,在Spring Boot官方没有整合这些技术框架的情况下
MyBatis、Druid等技术框架所在的开发团队主动与Spring Boot框架进行了整合,实现了各自的依赖启动器
例如mybatis-spring-boot-starter、druid-spring-boot-starter等
我们在pom.xml文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号(因为一般spring boot的对应没有该版本的配置)
自动配置:
在这里需要提一点,一般不同的版本的boot,对应的代码显示是不同的(上面的依赖管理的内容可能也会不同)
但大致底层原理是一样的,后面有时会说明一下,所以注意即可
概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置
我们无需配置或者只需要少量配置就能运行编写的项目
问题:Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置:
Spring Boot应用的启动入口是@SpringBootApplication注解标注的类中的main()方法
package com.lagou;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Springbootdome2Application {public static void main(String[] args) {SpringApplication.run(Springbootdome2Application.class, args);}}
进入到@SpringBootApplication内,观察其做了哪些工作(部分,前面的导入和包就省略了,后面也是如此):
@Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime(RUNTIME)表示运行时
@Documented //表示注解可以记录在javadoc中
@Inherited //表示可以被子类继承该注解
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {// 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};// 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};// 指定扫描包,参数是包名的字符串数组@AliasFor(annotation = ComponentScan.class,attribute = "basePackages")String[] scanBasePackages() default {};// 扫描特定的包,参数类似是Class类型数组@AliasFor(annotation = ComponentScan.class,attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = ComponentScan.class,attribute = "nameGenerator")Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}
从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息
我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解
关于这三个核心注解的相关说明具体如下(第三个:
@SpringBootConfiguration注解:
@SpringBootConfiguration:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类
查看@SpringBootConfiguration注解源码,核心代码具体如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 配置类的作用等同于配置文件,配置类也是容器中的一个对象
@Indexed
public @interface SpringBootConfiguration {@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}
从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration
该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描
由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类
只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已,这样会使得该类可以被获取调用(底层获取执行)
从而执行main方法
虽然我们也可以再次进行获取,但也要注意,同一个类里面,多个不同的变量,基本是能注入同一个对象的
@EnableAutoConfiguration注解:
@EnableAutoConfiguration:开启自动配置功能,以前由我们需要配置的东西,现在由SpringBoot帮我们自动配置
这个注解就是Springboot能实现自动配置的关键
同样,查看该注解内部查看源码信息,核心代码具体如下 :
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// Spring的底层注解@Import,给容器中导入一个组件
// 导入的组件是AutoConfigurationPackages.Registrar.class
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";// 返回不会被导入到 Spring 容器中的类Class<?>[] exclude() default {};// 返回不会被导入到 Spring 容器中的类名String[] excludeName() default {};
}
可以发现它是一个组合注解, Spring 中有很多以Enable开头的注解
其作用就是借助@Import来收集并注册特定场景相关的Bean,并加载到IOC容器
@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器
下面,对这两个核心注解@AutoConfigurationPackage和@Import分别进行讲解:
@AutoConfigurationPackage注解:
查看@AutoConfigurationPackage注解内部源码信息,核心代码具体如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class}) // 导入Registrar中注册的组件(实例)
public @interface AutoConfigurationPackage {String[] basePackages() default {};Class<?>[] basePackageClasses() default {};
}
从上述源码可以看出,@AutoConfigurationPackage注解的功能主要是由@Import注解实现的
它是spring框架的底层注解,它的作用就是给容器中导入某个组件(组件可以说是实例)类
例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中
可查看Registrar类中registerBeanDefinitions方法
这个方法就是导入组件类的具体实现(ctrl+鼠标左键点击Registrar.class中的Registrar):
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Registrar() {}public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));//低版本一般是/*AutoConfigurationPackages.register(registry,
(new AutoConfigurationPackages.PackageImports(metadata)).getPackageName());*/}public Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));}
}
从上述源码可以看出,在Registrar类中有一个registerBeanDefinitions()方法
使用Debug模式启动项目(记得指定对应的位置,AutoConfigurationPackages.register方法)

查看对应的metadata(一般包括注解操作的信息,一般指向对应总注解对应的类,这里是Springbootdome2Application类):

我们发现,通过注解,的确得到了当前类的对应的包com.lagou(实际上是结合了@ComponentScan注解,而得到的地址信息)
也就是说,@AutoConfigurationPackage注解的主要作用就是将主程序类所在包及所有子包下的组件扫描到spring容器中
因为他操作了@Import注解,一般是将实例放入到ioc容器中的操作
该注解在这里一般会操作执行参数类的方法,好像是固定的几个
如selectImports方法包括内部类的,可能也有registerBeanDefinitions方法
因此 在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置
然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫描
@Import({AutoConfigurationImportSelector.class})注解:
将AutoConfigurationImportSelector这个类导入到Spring容器中
AutoConfigurationImportSelector可以帮助Springboot应用
将所有符合条件的@Configuration配置(配置类)都加载到当前SpringBoot创建并使用的IOC容器(ApplicationContext)中
如果说@AutoConfigurationPackage注解是扫描得到实例,但却不能操作配置类
那么这个@Import({AutoConfigurationImportSelector.class})注解是操作配置类得到实例,但一般是需要先进行扫描
他们一起,使得扫描得到实例
好像spring中扫描时,他们是一起操作的,而不是分开,spring boot却是分开,但总体是一起
他们两个都需要操作完才会真正的启动
继续研究AutoConfigurationImportSelector这个类
通过源码分析这个类中是通过selectImports这个方法告诉springboot都需要导入那些组件:
 public String[] selectImports(AnnotationMetadata annotationMetadata) {//判断EnabledAutoConfiguration注解有没有开启,默认开启if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);//旧版本中AutoConfigurationImportSelector.AutoConfigurationEntry是AutoConfigurationEntryreturn StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}
一般的,他应该有这个方法:
//得到自动配置元信息,需要传入beanClassLoader这个类加载器
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//旧版本中,this.autoConfigurationMetadata是AutoConfigurationMetadata autoConfigurationMetadata
在另外一个selectImports方法里面的this.getAutoConfigurationMetadata()方法里面,他们基本是一起操作的
但有些版本,大概是低版本,一般全部在同一个方法里面,即String[] selectImports方法里面,如:

深入研究loadMetadata方法(无论新版本还是从前版本基本都是一样的):
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");}
//低版本的一般将"META-INF/spring-autoconfigure-metadata.properties"赋值给了变量,然后这里传递变量static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {try {/*读取spring-boot-autoconfigure-2.1.5.RELEASE.jar包中的spring-autoconfigure-metadata.properties的信息从而生成url,对应jar包的版本可能与导入的spring boot依赖有关*/Enumeration<URL> urls = classLoader != null ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);Properties properties = new Properties();//解析urls枚举对象中的信息封装成properties对象并加载while(urls.hasMoreElements()) {properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource((URL)urls.nextElement())));}//根据封装好的properties对象生成AutoConfigurationMetadata对象返回return loadMetadata(properties); //下面的方法} catch (IOException var4) {throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4);}}static AutoConfigurationMetadata loadMetadata(Properties properties) {return new AutoConfigurationMetadataLoader.PropertiesAutoConfigurationMetadata(properties);}
对应的META-INF/spring-autoconfigure-metadata.properties文件地址:
点击:

对应的部分文件信息:

在里面的文件信息里面随便找一个:
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=
org.springframework.amqp.rabbit.annotation.EnableRabbit#其中org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration
#第一个点,往前推
#代表进行自动配置的类,后面的ConditionalOnClass是一个注解,该注解一般表示的是条件(=后面的)
#如果要向ioc容器中注入我们自动配置的类需要满足=号后面的条件
#具体条件是
#当该注解里面出现了后面的org.springframework.amqp.rabbit.annotation.EnableRabbit(EnableRabbit这个类时)
#就进行该RabbitAnnotationDrivenConfiguration类的自动注入
#自动注入:可以说成是自动的创建实例,放在ioc容器里面
#即使得可以被注入得到,所以我们导入对应的依赖,spring boot会帮我们生成实例,就是这样的原因,但并不是所有依赖,因为该文件的内容是有限的,这是肯定的#其他的基本都是这样的说明
至此,上面的操作总得来说是得到所有的自动配置类及其需要的对应条件
至此该方法介绍完毕,接下来我们接着看selectImports方法里面的getAutoConfigurationEntry
AutoConfigurationImportSelector类 getAutoConfigurationEntry方法:
/*
低版本的一般是protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {
多了个AutoConfigurationMetadata autoConfigurationMetadata
*/
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {//判断EnabledAutoConfiguration注解有没有开启,默认开启if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {//获得注解的属性信息(如前面的扫描时得到的包地址信息,从而可以找到对应的配置类进行比较)AnnotationAttributes attributes = this.getAttributes(annotationMetadata);//获取默认支持的自动配置类列表List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//去重configurations = this.removeDuplicates(configurations);//去除一些多余的配置类,根据EnabledAutoConfiguratio的exclusions属性进行排除Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);//新的版本,一般会加上this//如旧版本会是checkExcludedClasses(configurations, exclusions);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);//根据pom文件中加入的依赖文件筛选中最终符合当前项目运行环境对应的自动配置类(比较)//低版本这里就是configurations = filter(configurations, autoConfigurationMetadata);//传入了条件(autoConfigurationMetadata),进行比较//根据配置类找到相同的信息,若满足条件,则自动配置,从而操作默认的配置类//但基本只能操作他里面规定过的(基本是常用的,所以,并不是所有的依赖,都会自动配置)//好像一般会有自动的扩展(满足条件的),使得可以操作,所以配置类基本都可以,就如扫描一样//他们两个的作用基本是一样的//this.getConfigurationClassFilter()里面可以得到autoConfigurationMetadata数据,从而进行比较configurations = this.getConfigurationClassFilter().filter(configurations);//触发自动配置导入监听事件this.fireAutoConfigurationImportEvents(configurations, exclusions);//低版本这里是return new AutoConfigurationEntry(configurations, exclusions);return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}}//注意:不同的版本对应的代码操作可能有些微调,但是总体的作用还是一样的
深入getCandidateConfigurations方法(上面的:获取默认支持的自动配置类列表这个注释):
 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {//loadFactoryNames方法传入了两个参数//this.getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class//this.getBeanClassLoader()返回的是beanClassLoader(类加载器,在前面我们也知道对应的条件就操作了他)//使用了内部的工具类SpringFactoriesLoader操作方法进行读取文件信息List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(),this.getBeanClassLoader()));ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");return configurations;}protected Class<?> getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;}protected ClassLoader getBeanClassLoader() {return this.beanClassLoader;}
继续点开loadFactoryNames方法(上面的注释操作的方法):
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)
{ClassLoader classLoaderToUse = classLoader;if (classLoader == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}//获取出入的键String factoryTypeName = factoryType.getName();return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());}
//有些老版本是这个
/*
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader)
{
//获取出入的键
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,
Collections.emptyList());
}我们可以发现,的确是差不多的,只是进行了微调*/
我们再次点开loadSpringFactories方法:
 private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {Map<String, List<String>> result = (Map)cache.get(classLoader);if (result != null) {return result;} else {HashMap result = new HashMap();try {//如果类加载器不为null,则加载类路径下spring.factories文件//将其中设置的配置类的全路径信息封装 为Enumeration类对象Enumeration urls = classLoader.getResources("META-INF/spring.factories");//循环Enumeration类对象,根据相应的节点信息生成Properties对象//通过传入的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = properties.entrySet().iterator();while(var6.hasNext()) {Entry<?, ?> entry = (Entry)var6.next();String factoryTypeName = ((String)entry.getKey()).trim();String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());String[] var10 = factoryImplementationNames;int var11 = factoryImplementationNames.length;for(int var12 = 0; var12 < var11; ++var12) {String factoryImplementationName = var10[var12];((List)result.computeIfAbsent(factoryTypeName, (key) -> {return new ArrayList();})).add(factoryImplementationName.trim());}}}result.replaceAll((factoryType, implementations) -> {return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));});cache.put(classLoader, result);return result;} catch (IOException var14) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);}}}
会去读取一个 spring.factories 的文件
读取不到会表示对应的这个错误,我们根据类变量会看到,最终路径的长这样
public final class SpringFactoriesLoader {public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";//实际上有些版本会直接使用这个变量,而不会写上具体值,这里是具体值
上面的方法,总体来说是去加载一个外部的文件,而这文件是在如下
与前面的META-INF/spring-autoconfigure-metadata.properties在同一个目录下
他们两个基本是有对照的,因为都是操作实例,只是一个扫描操作,一个配置类操作而已

对应的部分文件信息:

至此得到了默认的支持的自动配置类列表,而基本不用去比较条件触发(有的话)
后面的操作自然是操作配置类,使得条件成立的放入ioc容器,这也使得我们只需要导入对应的依赖即可自动的配置好,而不用扫描放入IOC容器了
所以说@EnableAutoConfiguration注解中操作配置类的注解就是从classpath中搜寻META-INF/spring.factories配置文件
并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项
通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的配置类,并加载到IOC容器中
当然这是默认支持的放入
一般默认的都会包括常用的,所以这是核心
后面的筛选主要是为了不加载多余的其他默认配置,以及满足条件的,但没有默认支持的加入IOC容器里面
因为我没有对应的依赖总不能都加载吧,从而加入ioc容器
即加上后面还有进行筛选,使得配置类操作完毕
至此一个操作扫描,一个操作配置类,使得扫描创建实例(虽然spring中的扫描基本也是如此,但他好像是一起的)
以刚刚的项目为例,举个例子:
在项目中加入了Web环境依赖启动器
对应的WebMvcAutoConfiguration自动配置类就会生效(有依赖的话,基本会满足条件),打开该自动配置类会发现
在该配置类中通过全注解配置类的方式对Spring MVC运行所需环境进行了默认配置
包括默认前缀、默认后缀、视图解析器、MVC校验器等
而这些自动配置类的本质是传统Spring MVC框架中对应的XML配置文件
只不过在Spring Boot中以自动配置类的形式进行了预先配置
因此,在Spring Boot项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序
当然,我们也可以对这些自动配置类中默认的配置进行更改
总结
因此springboot底层实现自动配置的步骤是:
1: springboot应用启动;
2:@SpringBootApplication起作用;
3:@EnableAutoConfiguration:
@AutoConfigurationPackage:这个组合注解主要是@Import(AutoConfigurationPackages.Registrar.class)
它通过将Registrar类导入到容器中
而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中(也就是扫描得到实例)
@Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中
AutoConfigurationImportSelector类作用是通过selectImports方法执行的过程中
会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories进行加载
实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程(操作配置类得到实例)
至此,包地址信息使得扫描和操作配置类得到实例的操作完毕,即自动配置完成
最后说明一下最后一个核心注解,@ComponentScan注解
@ComponentScan注解 :
@ComponentScan注解使得具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决定
在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行操作解析
从而得到Spring Boot项目主程序启动类所在包的具体位置
也就是说他使得对应的地址,是当前所在的包,但参数一般由@AutoConfigurationPackage解析,而不是直接的定义
至此@SpringBootApplication 的注解的功能就分析差不多了, 简单来说就是 3 个注解的组合注解:
/*
@SpringBootConfiguration@Configuration  //通过javaConfig的方式来添加组件到IOC容器中(当前为配置类)使得可以被获取(底层获取执行),从而调用main方法执行
@EnableAutoConfiguration  //最核心的注解@AutoConfigurationPackage //自动配置包,与@ComponentScan扫描到的添加到IOC@Import(AutoConfigurationImportSelector.class) //到META-INF/spring.factories中定义的bean添加到IOC容器中(一般都是配置类)
@ComponentScan //包扫描,得到地址,给@AutoConfigurationPackage
然后信息也会给@Import(AutoConfigurationImportSelector.class) 操作(进行比较)
SpringBoot数据访问:
Spring Boot整合MyBatis:
MyBatis 是一款优秀的持久层框架,Spring Boot官方虽然没有对MyBatis进行整合(所以并不是所有依赖spring boot都操作了,因为对应文件内容有限,这是肯定的)
但是MyBatis团队自行适配了对应的启动器,进一步简化了使用MyBatis进行数据的操作
因为Spring Boot框架开发的便利性,所以实现Spring Boot与数据访问层框架(例如MyBatis)的整合非常简单
主要是引入对应的依赖启动器,并进行数据库相关参数设置即可
基础环境搭建:
数据准备
在MySQL中,先创建了一个数据库springbootdata,然后创建了两个表t_article和t_comment并向表中插入数据
其中评论表t_comment的a_id与文章表t_article的主键id相关联
-- 创建数据库
CREATE DATABASE springbootdata;
-- 选择使用数据库
USE springbootdata;
-- 创建表t_article并插入相关数据
DROP TABLE IF EXISTS t_article;
CREATE TABLE t_article (      id int(20) NOT NULL AUTO_INCREMENT COMMENT '文章id',      title varchar(200) DEFAULT NULL COMMENT '文章标题',      content longtext COMMENT '文章内容',      PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO t_article VALUES ('1', 'Spring Boot基础入门', '从入门到精通讲解...');
INSERT INTO t_article VALUES ('2', 'Spring Cloud基础入门', '从入门到精通讲解...');        -- 创建表t_comment并插入相关数据
DROP TABLE IF EXISTS t_comment;
CREATE TABLE t_comment (      id int(20) NOT NULL AUTO_INCREMENT COMMENT '评论id',      content longtext COMMENT '评论内容',      author varchar(200) DEFAULT NULL COMMENT '评论作者',      a_id int(20) DEFAULT NULL COMMENT '关联的文章id',      PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;  INSERT INTO t_comment VALUES ('1', '很全、很详细', 'lucy', '1');
INSERT INTO t_comment VALUES ('2', '赞一个', 'tom', '1');
INSERT INTO t_comment VALUES ('3', '很详细', 'eric', '1');
INSERT INTO t_comment VALUES ('4', '很好,非常详细', '张三', '1');
INSERT INTO t_comment VALUES ('5', '很不错', '李四', '2');
创建项目,引入相应的启动器:

选择这两个,一般这里的选择都是依赖的集合(多个依赖的总体),当然了若你要操作界面,那么就点击对应的web的依赖
对应的依赖:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><!--固定的--><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.lagou</groupId><artifactId>bootmybatis</artifactId><version>0.0.1-SNAPSHOT</version><name>bootmybatis</name><description>bootmybatis</description><properties><java.version>11</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--引入spring boot mybatis的启动器--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><!--mysql驱动包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--如果出现了<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency>基本上就是初始项目,代表手动的添加依赖都没有加--><dependency><!--固定的--><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><!--固定的--><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
记得删除不需要的文件
编写与数据库表t_comment和t_article对应的实体类Comment和Article:
具体目录:

Comment类:
package com.lagou.bootmybatis.pojo;/****/
public class Comment {private Integer id;private String content;private String author;private Integer aId;@Overridepublic String toString() {return "Comment{" +"id=" + id +", content='" + content + '\'' +", author='" + author + '\'' +", aId=" + aId +'}';}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}public Integer getaId() {return aId;}public void setaId(Integer aId) {this.aId = aId;}
}
Article类:
package com.lagou.bootmybatis.pojo;/****/
public class Article {private Integer id;private String title;private String content;@Overridepublic String toString() {return "Article{" +"id=" + id +", title='" + title + '\'' +", content='" + content + '\'' +'}';}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}
}
编写配置文件:
在application.yml(将原来的application.properties修改成application.yml),因为好观察,这个配置文件中进行数据库连接配置
# MySQL数据库连接配置
spring:datasource:url: jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC&characterEncoding=UTF-8username: rootpassword: 123456
注解方式整合Mybatis:
需求:实现通过ID查询Comment信息
在对应的bootmybatis包下面创建mapper包
创建一个对t_comment表数据操作的接口CommentMapper
package com.lagou.bootmybatis.mapper;import com.lagou.bootmybatis.pojo.Comment;
import org.apache.ibatis.annotations.Select;/****/
public interface CommentMapper {@Select("select * from t_comment where id = #{id}")public Comment findById(Integer id);}
虽然定义了注解,但是我们需要专门扫描他的操作(前面的扫描是操作实例,而不是这个,不是spring管的)
在对应的启动类上面加上如下注解:
package com.lagou.bootmybatis;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.lagou.bootmybatis.mapper")
//一般来说需要mybatis和spring的整合依赖
//当然也要有myabtis的依赖(因为需要整合自然也要mybatis的)和使用连接的依赖(如jdbc)
//否则会报错,使得启动也会失败,但是这个是spring boot项目,他并不会直接的操作依赖
//需要与spring boot进行整合(不需要整合的一般不用加对应的spring boot依赖,如驱动依赖)
//所以,直接的导入逻辑上并没有问题,但却启动不了(操作不了)
//即需要我们直接导入spring boot对应的整合mybatis的依赖
//比如mybatis-spring-boot-starter,里面包含了对应的依赖(一个总体)
//即包括了上面说的所有依赖,即不需要导入上面的依赖了
//而@Mapper注解只需要mybatis依赖即可
//扫描该包下的mybatis的相关注解,实际上就是加载,只是会操作注解而已,相当于给对应的包的类都加上@Mapper注解
//实际上mybatis扫描对应的xml时,也会对对应的路径的接口进行操作(单独的时候,使得可以操作注解)
public class BootmybatisApplication {public static void main(String[] args) {SpringApplication.run(BootmybatisApplication.class, args);}}
编写测试方法(快速构建时创建的那个测试类):
package com.lagou.bootmybatis;import com.lagou.bootmybatis.mapper.CommentMapper;
import com.lagou.bootmybatis.pojo.Comment;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest
class BootmybatisApplicationTests {@Autowiredprivate CommentMapper commentMapper;  //可能会报红,idea检查的问题,不用管,因为运行时不会报错//有操作注入的(spring和mybatis整合的操作)//spring boot操作的@Testvoid contextLoads() {Comment byId = commentMapper.findById(1);System.out.println(byId);}}
若返回数据,则操作成功,但在这之前,我们需要解决对应的数据库的下划线,防止没有得到数据
因为这时控制台中查询的Comment的aId属性值为null,没有映射成功
这是因为编写的实体类Comment中使用了驼峰命名方式将t_comment表中的a_id字段设计成了aId属性,所以无法正确映射查询结果
了解决上述由于驼峰命名方式造成的表字段值无法正确映射到类属性的情况
可以在Spring Boot全局配置文件application.yml中添加开启驼峰命名匹配映射配置,示例代码如下
#开启驼峰命名匹配映射
mybatis:configuration:map-underscore-to-camel-case: true
#相当于数据库的字段,去除对应的下划线的匹配,或者说,只识别有效数字
#如字母,汉字等等,汉字一般会识别(大小写忽略,一般对汉字并没有什么作用),但最好不要使用
至此对应的信息就匹配了
配置文件的方式整合MyBatis:
在这之前我们一般是这样的操作
创建一个用于对数据库表t_article数据操作的接口ArticleMapper:
package com.lagou.bootmybatis.mapper;import com.lagou.bootmybatis.pojo.Article;
import org.apache.ibatis.annotations.Mapper;/****/
@Mapper //该注解,一般在mybatis操作启动时,会进行查找(整个项目找),但单独的并不会有对应的作用
//当与spring整合时,会创建当前接口的实例,放到ioc容器里面,找到@Mapper后,扫描当前类
//那么对应的注解也会起作用
//即该实例可以操作对应的被注解操作的方法,所以一般与spring结合使用
public interface ArticleMapper {public Article selectArticle(Integer id);
}
创建XML映射文件:
resources目录下创建一个统一管理映射文件的包mapper,并在该包下编写与ArticleMapper接口方应的映射文件ArticleMapper.xml
<?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.lagou.mapper.ArticleMapper"><select id="selectArticle" resultType="Article">select * from Article where id = #{id}</select></mapper>
一般情况下,我们并不会用到@mapper注解,而是扫描对应的xml来进行操作,我们可以知道上面的手动写是有点麻烦
接下来我们说一种方便的操作,来生成上面的代码
安装Free Mybatis plugin插件生成对应的代码
选择如下:

一般情况下,可能找不到相同的名称的插件,实际上只要你找到的插件的介绍有对应的功能即可,如下面的第一个Free MyBatis Tool

虽然前面85章我们安装了一个Easy code也可以操作对应的文件,但这里再次给出一个插件来使用,了解即可

安装之后也会多一个选项,这里是对应的数据库连接地方(具体看看看85章博客)
Mybatis-Generator实际上操作的方式与EasyCode是差不多的,点击后,会出现如下:

修改后,就是如下:

点击ok,查看对应的文件是否生成,一般会进行覆盖相同的文件(应该有提示)
进行检查对应生成的文件,检查完毕后,那么就操作完成了
配置XML映射文件路径:
在项目中编写的XML映射文件,Spring Boot并无从知晓,所以无法扫描到该自定义编写的XML配置文件
还必须在全局配置文件application.yml中添加MyBatis映射文件路径的配置
同时需要添加实体类别名映射路径,示例代码如下(在前面的基础上进行添加代码)
#开启驼峰命名匹配映射
mybatis:configuration:map-underscore-to-camel-case: true#配置MyBatis的xml配置文件路径,就使得该mapper文件夹下的所有文件进行加载,一般需要指定到具体文件,而不是目录mapper-locations: classpath:mapper/*.xml#配置XML映射文件中指定的实体类的别名路径,操作别名type-aliases-package: com.lagou.bootmybatis.pojo
#注意位置,位置不对,虽然可能并不会提示错误,但运行时会出现错误
#如:下面就会报错
#开启驼峰命名匹配映射
mybatis:configuration:map-underscore-to-camel-case: true#配置MyBatis的xml配置文件路径,就使得该mapper文件夹下的所有文件进行加载
mapper-locations: classpath:mapper/*.xml#配置XML映射文件中指定的实体类的别名路径,操作别名
type-aliases-package: com.lagou.bootmybatis.pojo
#这样就会报错#这里说明一下,mapper-locations和type-aliases-package是一个地方的配置
#他们在这里有一个特点是(好像是规定的,一般是第一个决定),必须在同一级别,否则报错
#在不同的级别有不同的作用,但级别不能是最高级别(如上面的)
#假设在configuration级别下,那么我们需要将配置文件的路径与接口路径保持一致
#其内容基本都要保持一致(在61章博客说过了)
#假设是如下:
#开启驼峰命名匹配映射
mybatis:configuration:map-underscore-to-camel-case: true#配置MyBatis的xml配置文件路径,就使得该mapper文件夹下的所有文件进行加载mapper-locations: classpath:mapper/*.xml#配置XML映射文件中指定的实体类的别名路径,操作别名type-aliases-package: com.lagou.bootmybatis.pojo
#与configuration同级别(即mybatis的下一级),那么对应的地址可以不一致,因为会匹配映射,但其他的内容需要一致
#具体的一致可以看看61章博客的内容
编写单元测试进行接口方法测试:
     //记得若设置的参数位置在configuration级别下//虽然你提供了配置文件的地址//包地址路径,当前项目开始的,这里简称为地址//如在资源文件下,mapper/ArticleMapper.xml,这个地址也就是包路径地址//但是spring整合mybatis时是需要操作对应的接口地址的//需要配置文件的地址与接口一致,且对应的文件名称也要一致,最好全部都一致//否则基本会报错,当然同级别的话,包地址路径可以不一致@Autowiredprivate ArticleMapper articleMapper;@Testvoid findArticleMapperById() {Article article = articleMapper.selectByPrimaryKey(1);System.out.println(article);}
至此若返回数据,则操作完毕
总体来说,spring boot封装了一些自动的配置,但有些并没有
但封装的这些,却大大的提高了我们的开发操作(虽然运行可能会更加慢些,但基本是启动的运行,而不是启动后的)
Spring Boot整合Redis
添加Redis依赖包:
在项目的pom.xml中添加如下:
<!-- redis依赖包 -->
<dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置Redis数据库连接:
在application.properties中配置redis数据库连接信息,如下(总体的配置):
# MySQL数据库连接配置
spring:datasource:url: jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC&characterEncoding=UTF-8username: rootpassword: 123456
#这里就是redis的配置了,通常是自动连接的,也就是说,后启动redis,会连接上的(循环的)redis:host: 192.168.164.128 #redis注解配置port: 6379 #端口号
#开启驼峰命名匹配映射
mybatis:configuration:map-underscore-to-camel-case: true#配置MyBatis的xml配置文件路径,就使得该mapper文件夹下的所有文件进行加载mapper-locations: classpath:mapper/*.xml#配置XML映射文件中指定的实体类的别名路径,操作别名type-aliases-package: com.lagou.bootmybatis.pojo
#注意位置,位置不对,虽然可能并不会提示错误,但运行时会出现错误
编写Redis操作工具类:
在bootmybatis包下创建util包,并创建RedisUtils类:
package com.lagou.bootmybatis.util;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;/****/
@Component
public class RedisUtils {//在80章博客操作时//对应的实例操作的变量实际上是他这个类的子类(StringRedisTemplate extends RedisTemplate)//也就是说RedisTemplate是父类,那么注入给RedisTemplate的类基本不会被StringRedisTemplate得到//而注入给StringRedisTemplate的实例也是属于他自己的,我们即他们的实例是不相同的//但他们却有个特点,当单独注入时,另外一个的实例不会给他(或者说没有创建),但当他们一起时,那么都创建了//而正是因为RedisTemplate是父类,则他会得到两个实例,从而报错//但当时需要对应的方法,所以就操作StringRedisTemplate了(继承了RedisTemplate)//我们也最好不要操作字符串时使用RedisTemplate,因为这时StringRedisTemplate比较方便//但在写入对象的类型时,一般只能操作RedisTemplate//因为StringRedisTemplate的value一般只能操作String,所有这里使用RedisTemplate//但RedisTemplate他给redis中set设置的key或者value基本是乱码的(set的时候,其他的操作好像并不会)//为什么这样说呢,在设置的值中,对应编码与存放的redis数据库的数据编码不一致//但对于的get还是可以得到(因为反过来得到的结果刚好就是对应的结果)//因为同样的操作,只是我们在服务器里查询时,是乱码,这是显示的问题//假设我们使用这个传入key是44,value是100的key//那么一般情况下,服务器的显示会出现"\xac\xed\x00\x05t\x00\x0244"//我们也可以使用get "\xac\xed\x00\x05t\x00\x0244"要加上""//大概是因为解析显示的原因,因为真实的数据可能就是""\xac\xed\x00\x05t\x00\x0244""(编码的问题)//而不是"\xac\xed\x00\x05t\x00\x0244"(一般的会默认加上""的)//所以get \xac\xed\x00\x05t\x00\x0244得不到//从而得到"\xac\xed\x00\x05t\x00\x03100"(有趣的是,后缀一般是真的值,如这里的100)//我们会发现,显示出来的是乱码,为什么44变成乱码呢,原因是我们传入的44中并没有是编码一致造成的数据//从而44没有解析出来,造成乱码/*下面给出一个图形:(假设为a,b,c编码,注意:这只是假设,是为了更好的理解)a(java程序,使用a编码) -- (使用b编码,redis数据库的数据)b(服务器的查看,出现c编码的显示,出现乱码)*///而StringRedisTemplate不会,相当于操作的就是b编码//若要解决他的乱码,那么需要如下的设置代码:/*//设置序列化Key的实例化对象redisTemplate.setKeySerializer(new StringRedisSerializer());//设置序列化Value的实例化对象,若没有加上这个//那么当value是类对象时(对应的类是没有实现Serializable接口的类),那么就会报错实际上是因为参数的原因所以实际上key也可以设置,后面会有解释redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());当然,他们也可以只操作一个,即可以是单独的,只是另外一个不会操作了而已注意:上面的new StringRedisSerializer()和new GenericJackson2JsonRedisSerializer()这两个参数实际上是一种方式,后面的解释是以上面的为主因为new GenericJackson2JsonRedisSerializer()基本可以操作任何类型而new StringRedisSerializer()只能操作String类型但是其中new GenericJackson2JsonRedisSerializer()会看出整体所以会出现后面的"\"100\"",而不是"100",当然,正是因为这样所以实际上对于key来说,"\"100\""不是"100",可以修改上面的两个参数后,添加就知道了出现了两个key,因为本来就是不同(特别是显示)但是为了更好的观察,以及可以操作类的类型所以通常value需要设置new GenericJackson2JsonRedisSerializer()而key我们只需要观察即可,所以通常key需要设置new StringRedisSerializer()设置好后,就不会乱码了(相对于普通的数据来说,如数字和字母等等)可以将上面理解成,将a编码变成对应的b编码(key和value都操作,而不是操作一个)但是对应的get却没有操作成功,因为他的值在过去选择时,还是使用的是a编码,如何解决呢也加上上面的代码即可,若是同一个redisTemplate,且加上过,那么可以不用在写了但他们两个StringRedisTemplate和RedisTemplate操作中文的数据时还是会出现乱码的,但他真的是数据的乱码还是显示的乱码呢这里就有两个概念,显示的乱码和数据的乱码显示的乱码的说明很简单,随便在一个文件里面,修改文件的编码格式,会发现对于的数据的显示基本不同,因为二进制都是一样的,只是显示的数据不同而已数据的乱码,由于编码不同,那么对应传递的数据的结果一般不同如44变成了"\xac\xed\x00\x05t\x00\x0244"实际上是显示的乱码,也就是说b编码可以操作中文,只是使用了c编码的显示,所以中文就是乱码,我们可以假设你加上了key是还会,value是100(字符串)的数据,一般服务器显示"\xe8\xbf\x98\xe4\xbc\x9a"使用get "\xe8\xbf\x98\xe4\xbc\x9a"会得到"\"100\"",该值操作对应的编码时也就是使用了上面的两个序列化代码时,会将"100"整体算入数据,这是编码的问题只是他由key的问题变成了value的问题,但是并没有关系,起码没有乱码所以是这样的数据,虽然StringRedisTemplate也可以加上这样的代码,多出了操作序列化,使得也是整体放入但也只能看到value的不同结果,而不会出现key的不同结果(因为key操作没操作都是一样的结果)你可以试着将对应的代码删除,会发现,删除后的显示是"100"没有删除的是100(因为反过来就是你设置的,做反操作,否则就是对应的真正数据100)也就是说对应的值是得到的,只是因为服务器的显示有问题,因为是c编码,那么如何变成b编码呢主要是改变对应的客户端的编码,就如这里的手动设置一样那么使用redis-cli --raw执行客户端,那么就使用的是b编码了,那么就可以看到中文了当然,若还有a编码的操作的数据,自然也会出现中文的至此,redis的中文问题及其java的设置和获取问题的操作中文都解决完毕实际上我们并不需要过分的关注他们,因为如果不看显示的话,那么结果自然会精确的得到因为互相来说,编码路径相同,只是显示操作了其他编码,所以如果不看显示,路径相同,自然数据也就是一致的*/@Autowiredprivate RedisTemplate redisTemplate;/*读取缓存(对于redis来说,就是缓存)*/public Object get(final String key) {return redisTemplate.opsForValue().get(key);}/*写入缓存*/public boolean set( String key, Object value) {boolean result = false;try {redisTemplate.opsForValue().set(key, value,1, TimeUnit.DAYS);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*更新缓存*/public boolean getAndSet(final String key, String value) {boolean result = false;try {redisTemplate.opsForValue().getAndSet(key, value);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*删除缓存*/public boolean delete(final String key) {boolean result = false;try {redisTemplate.delete(key);result = true;} catch (Exception e) {e.printStackTrace();}return result;}}
测试:
在对应的测试类里加上方法:
package com.lagou.bootmybatis;import com.lagou.bootmybatis.mapper.ArticleMapper;
import com.lagou.bootmybatis.mapper.CommentMapper;
import com.lagou.bootmybatis.pojo.Article;
import com.lagou.bootmybatis.pojo.Comment;
import com.lagou.bootmybatis.util.RedisUtils;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.util.ArrayList;@RunWith(SpringRunner.class)
@SpringBootTest
class BootmybatisApplicationTests {@Autowiredprivate CommentMapper commentMapper;@Testvoid contextLoads() {Comment byId = commentMapper.findById(1);System.out.println(byId);}//记得位置一致,虽然你提供了配置文件的地址//但是spring整合mybatis时是需要操作对应的接口地址的//需要配置文件的地址与接口一致,且对应的文件名称也要一致@Autowiredprivate ArticleMapper articleMapper;@Testvoid findArticleMapperById() {Article article = articleMapper.selectByPrimaryKey(1);System.out.println(article);}/*写入,key:1,value:mysql数据库的id为1的acticle记录*/@Autowiredprivate RedisUtils redisUtils;@Testpublic void writeRedis(){boolean set = redisUtils.set("1", articleMapper.selectByPrimaryKey(1));System.out.println(set);}@Testpublic void readRedis(){Article article = (Article) redisUtils.get("1");System.out.println(article);//返回了对应的对象数据}}
对应的redis配置添加:
# MySQL数据库连接配置
spring:datasource:url: jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC&characterEncoding=UTF-8username: rootpassword: 123456redis:host: 192.168.164.128  #redis注解配置port: 6379  #端口号jedis:pool:max-active: 18 #设置连接池最大的连接数量max-wait: 3000 #连接池中最大的阻塞等待时间,3秒max-idle: 20 #连接池中最大的空闲连接数min-idle: 2 #连接池中最小的空闲连接数timeout: 3000 #连接的超时时间,3秒
#我们可以使用ctrl+鼠标左键,点击host,然后就可以看到对应的redis有哪些属性可以设置了
#发现有jedis,timeout等等
#当然并不是所有的都可以点击,一般需要是对应的注解,我们可以往上翻,可以看到有
#@ConfigurationProperties(
#    prefix = "spring.redis"
#)
#上面的内容,说明,在spring下的redis,发现正好对应层级,而没有的,自然点击不了(一般提示没有对应的项目文件)
至此,spring boot整合redis操作成功,实际上对应的注入的类
一般是操作连接池的(StringRedisTemplate和RedisTemplate两个基本都是)
SpringBoot视图技术:
支持的视图技术 :
前端模板引擎技术的出现,使前端开发人员无需关注后端业务的具体实现,只关注自己页面的呈现效果即可
并且解决了前端代码错综复杂的问题、实现了前后端分离开发
Spring Boot框架对很多常用的 模板引擎技术(如: FreeMarker、 Thymeleaf、 Mustache等)提供了整合支持
该技术一般对很多数据的加载时,直接生成静态的,使得访问速度非常块,如访问jd.com网站,搜索商品,点击一个商品,查看地址
我找到的就是https://item.jd.com/100003033647.html,发现他是静态的网站,我们也可以感受到,访问的速度非常块
因为是静态的(写死的页面),所以速度快,他就是使用了模板引擎的技术
Spring Boot不太支持常用的JSP模板,并且没有提供对应的整合配置
这是因为使用嵌入式Servlet容器的Spring Boot应用程序对于JSP模板存在一些限制 :
在Jetty和Tomcat容器中, Spring Boot应用被打包成war文件可以支持JSP
但Spring Boot默认使用嵌入式Servlet容器以JAR包方式进行项目打包部署,这种JAR包方式不支持JSP
如果使用Undertow嵌入式容器部署Spring Boot项目
也不支持JSP模板(Undertow 是红帽公 司开发的一款基于 NIO 的高性能 Web 嵌入式服务器)
Spring Boot默认提供了一个处理请求路径"/error"的统一错误处理器,返回具体的异常信息
使用JSP模板时,无法对默认的错误处理器进行覆盖,只能根据Spring Boot要求在指定位置定制错误页面
上面对Spring Boot支持的模板引擎进行了介绍,并指出了整合JSP模板的一些限制
接下来,对其中常用的Thymeleaf模板引擎进行介绍,并完成与Spring Boot框架的整合实现
Thymeleaf:
Thymeleaf是一种现代的基于服务器端的Java模板引擎技术,也是一个优秀的面向Java的XML、XHTML、 HTML5页面模板
它具有丰富的标签语言、函数和表达式,在使用Spring Boot框架进行页面设计时,一般会选择Thymeleaf模板
Thymeleaf语法 :
常用标签:
在HTML页面上使用Thymeleaf标签, Thymeleaf 标签能够动态地替换掉静态内容,使页面动态展示
为了大家更直观的认识Thymeleaf,下面展示一个在HTML文件中嵌入了Thymeleaf的页面文件,示例代码如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <!--需要xmlns:th="http://www.thymeleaf.org"才可以操作th-->
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" media="all" href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
<!--
这个样式在这里并没有使用(实际上好像并没有该样式,因为没有创建),所以可以删除,但写上也是没有问题的
只是不会有对应的作用而已,基本不会对页面造成影响
-->
<title>Title</title>
</head>
<body>
<p th:text="${hello}">欢迎进入Thymeleaf的学习</p>
</body>
</html>

上述代码中,"xmlns:th="http://www.thymeleaf.org"用于引入Thymeleaf模板引擎标签
使用关键字"th"标注标签是Thymeleaf模板提供的标签
其中,"th:href"用于引入外联样式文件,"th:text"用于动态显示标签文本内容(没有则操作默认值,一般是标签里面的)
除此之外,Thymeleaf模板提供了很多标签,接下来,通过一张表罗列Thymeleaf的常用标签

标准表达式:
Thymeleaf模板引擎提供了多种标准表达式语法,在正式学习之前,先通过一张表来展示其主要语法及说明

变量表达式 ${…}:
变量表达式${…}主要用于获取上下文中(作用域)的变量值,示例代码如下:
<p th:text="${title}">这是标题</p>
示例使用了Thymeleaf模板的变量表达式${…}用来动态获取P标签中的内容
如果当前程序没有启动,该片段会显示标签默认值"这是标题",若当前上下文中不存在title变量,一般返回空数据
因为对应的操作时,返回的就是null,如:
Object title = model.getAttribute("title");
System.out.println(title); //若没有则返回null,那么在前端显示的就是空值
如果当前上下文中存在title变量并且程序已经启动,当前P标签中的默认文本内容将会被title变量的值所替换
从而达到模板引擎页面数据动态替换的效果
同时,Thymeleaf为变量所在域提供了一些内置对象,具体如下所示

结合上述内置对象的说明,假设要在Thymeleaf模板引擎页面中动态获取当前国家信息,可以使用#locale内置对象,示例代码如下
The locale country is: <span th:text="${#locale.country}">US</span><!--
上述代码中,使用th:text="${#locale.country}"动态获取当前用户所在国家信息
其中标签内默认内容为US(美国),程序启动后通过浏览器查看当前页面时
Thymeleaf会通过浏览器语言设置来识别当前用户所在国家信息,从而实现动态替换 注意:模板操作对应的底层原理与jsp类似,也是java类,只是Thymeleaf快一些,且更加的符合html格式-->
至此我们可以测试一下:
Thymeleaf模板基本配置
首先 在Spring Boot项目中使用Thymeleaf模板,首先必须保证引入Thymeleaf依赖,示例代码如下:
<dependency>   <!--
使得可以操作对应的html变成模板,解析了数据后,然后返回前端显示,一般需要配置参数来确定格式
一起操作,这里提供类,配置则操作参数,类会使用参数,否则使用默认
-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
其次,在全局配置文件中配置Thymeleaf模板的一些参数,一般Web项目都会使用下列配置,示例代码如:
spring:thymeleaf:cache: true #启用模板缓存encoding: UTF-8 #模板编码mode: HTML5 #应用于模板的模板模式#下面两个表示开头和结尾prefix: classpath:/templates/ #指定模板页面存放路径suffix: .html #指定模板页面名称的后缀
若是properties文件的话,就是如下:
#若是properties的话,就是如下:
spring.thymeleaf.cache = true        #启用模板缓存
spring.thymeleaf.encoding = UTF_8    #模板编码
spring.thymeleaf.mode = HTML5        #应用于模板的模板模式
spring.thymeleaf.prefix = classpath:/templates/  #指定模板页面存放路径
spring.thymeleaf.suffix = .html      #指定模板页面名称的后缀
#实际上不难发现,对应的层级都是一层一层的,properties使用.代表层级,yaml(yml)使用":以及对应的级别"代表层级
#只是表达方式的差异而已,作用还是一样的
上述配置中,spring.thymeleaf.cache表示是否开启Thymeleaf模板缓存,默认为true
在开发过程中通常会关闭缓存,保证项目调试过程中数据能够及时响应
spring.thymeleaf.prefix指定了Thymeleaf模板页面的存放路径,默认为classpath:/templates/
spring.thymeleaf.suffix指定了Thymeleaf模板页面的名称后缀,默认为.html
静态资源的访问:
开发Web应用时,难免需要使用静态资源,Spring boot默认设置了静态资源的访问路径
使用Spring Initializr方式创建的Spring Boot项目,默认生成了一个resources目录
若在resources目录中有public、resources、static三个子目录,Spring boot默认会挨个从public、resources、static里面查找静态资源
当然,因为是静态资源,除了对应的js,css,图片,html等,其他的没有特殊含义的,都会当成是普通的文件
虽然前面的也是普通的文件,但是浏览器一般会特殊的处理
且现在的版本,对应的顺序可能是resources,static,public(不同的版本可能不同,但无关紧要)
因为不管怎么说,只要使用一个文件夹即可,通常使用static文件,因为见名知意
一般创建的项目只有static文件夹,其他两个文件夹没有
我们可以测试一下,造static里面创建index.js文件:
function sum(a,b){return a+b;
}
直接访问http://localhost:8080/index.js即可,即可以得到该信息
因为默认加上对应的三个子目录进行测试,如这里默认加上static,所以不要加上其他路径,否则一般会找不到文件,使得访问不了
即浏览器一般显示没有对应的网页信息
完成数据的页面展示:
创建Spring Boot项目,引入Thymeleaf依赖 :

当然对应的web也最好加上,操作页面
编写配置文件:
打开application.properties(修改成对应application.yaml)的全局配置文件
在该文件中对Thymeleaf模板页面的数据缓存进行设置
# thymeleaf页面缓存设置(默认为true),开发中方便调试应设置为false,上线稳定后应保持默认true    spring:thymeleaf:cache: falseencoding: UTF-8 #模板编码mode: HTML5 #应用于模板的模板模式prefix: classpath:/templates/ #指定模板页面存放路径suffix: .html #指定模板页面名称的后缀
#实际上,只要与第一个对齐或者小的级别即可,如spring,否则会报错
使用"spring.thymeleaf.cache=false"将Thymeleaf默认开启的缓存设置为了false,用来关闭模板页面缓存
创建web控制类:
由于spring boot不能直接的访问页面,且就算指定了对应的路径也不会过去访问的
像js,css,或者图片等等这些好像可以,因为一般在对应的静态文件夹下,开放的,其中html在静态文件夹下也可以访问
大概是spring boot禁止直接的访问不是静态资源文件的内容
注意:是不能直接的访问非静态资源文件的内容,而得到返回结果渲染页面显示给我们看
而前端控制器的控制类也可以操作访问,从而得到返回结果渲染页面显示给我们看
但一般情况下,我们通常使用对应的控制类操作地址
不像jsp一样,可以直接访问,基本无视路径(安全文件除外,那个时候基本需要控制器了)
且默认访问index.html或者index.jsp,当他们一起时,默认访问index.html:
在项目中创建名为com.lagou.controller的包(记得是启动类的当前包或者其子包)
并在该包下创建一个用于前端模板页面动态数据替换效果测试的访问实体类LoginController:
package com.lagou.thymeleaf.controller;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.Calendar;/****/
@Controller
public class LoginController {/** 获取并封装当前年份跳转到登录页login.html*/@RequestMapping("/toLoginPage")  public String toLoginPage(Model model){   //model底层就是request的操作(request域)model.addAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR)); //Calendar.getInstance().get(Calendar.YEAR),获取当前年份   return "login";  }
}
toLoginPage()方法用于向登录页面login.html跳转,同时携带了当前年份信息currentYear,是否发现,与jsp有点类似
因为模板操作他也是jsp的那个模式,底层都是java类来操作
创建模板页面并引入静态资源文件:
在对应的资源文件夹下可以看到templates文件夹,在里面创建login.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><link rel="stylesheet" type="text/css" media="all"href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" /><title>Title</title>
</head>
<body>
<!--可能会爆红,但他也只是检查而已,对运行并没有关系,不用理会-->
The locale country is: <span th:text="${#locale.country}">US</span><br>
The locale country is: <span th:text="${#locale.language}">中文</span><br><!--${currentYear}这个可能会爆红,但他也只是检查而已,对运行并没有关系,不用理会-->
<span th:text="${currentYear}">2019</span><br>
<span th:text="${currentYearr}">2019</span><br>
9<!--如果${}里面是对象,则结果是他的toString方法,若是对象.变量,则是调用该变量首字母大写的get方法
当然,若该变量的首字母本来大写,那么也是需要首字母大写的get方法,即不会变即假设是a.b,那么调用a对象的getB方法,无论是否有该变量,我们只获得getB方法的结果作为该结果没有对应的get方法,则报错,访问不了页面,当然,没有toString,自然使用父类的,到那时
也就是对应"类名@hashCode值"形式的字符串了
-->
</body>
</html>
返回的数据:

我们点击这里:

直接的运行,是不会操作模板的,我们发现,他的确进行了替换
至此测试完毕,后面的操作,可以自行加上进行测试
选择变量表达式 *{…}:
选择变量表达式和变量表达式用法类似,一般用于从被选定对象而不是上下文中获取属性值
如果没有选定对象,则和变量表达式一样,示例代码如下
<div th:object="${book}">
<p>titile: <span th:text="*{title}">标题</span></p>
</div>
*{title} 选择变量表达式获取当前指定对象book的title属性值
我们也进行测试一下:
在前端(页面文件)加上上面的代码,然后操作如下:
创建pojo包(对应的包一般与上面的controller包同一级),并创建book对象:
package com.lagou.thymeleaf.pojo;/****/
public class book {private String title;public book() {}public book(String title) {this.title = title;}@Overridepublic String toString() {return "book{" +"title='" + title + '\'' +'}';}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}
}
并在后端的toLoginPage方法下,加上如下代码:
  model.addAttribute("book", new book("111"));
重新部署,访问,会发现替换了值,则代表操作成功
注意:对应的title的值是通过get方法来获取的,且对应的get后面的名称必须是首字母大写,即getTitle()方法
假设没有对应的该get方法或者操作不到该方法(如没有对象,而是其他值等等,对应的值没有该方法)
会使得报错,那么页面也就访问不了了,但只要你的值中有该方法,则得到对应的返回值给前端,无论是否有该变量,即只看方法
无论是什么对象,比如a类,b类等等类都有该getTitle()方法,当他们的对象当成值时
他们两个都可以使得前端显示对应的该方法的返回值
消息表达式 #{…}:
消息表达式#{…}主要用于Thymeleaf模板页面国际化内容的动态替换和展示
使用消息表达式#{…}进行国际化设置时,还需要提供一些国际化配置文件
我们也进行测试一下:
在对应的配置文件中加上如下配置:
# thymeleaf页面缓存设置(默认为true),开发中方便调试应设置为false,上线稳定后应保持默认truespring:thymeleaf:cache: falseencoding: UTF-8 #模板编码mode: HTML5 #应用于模板的模板模式prefix: classpath:/templates/ #指定模板页面存放路径suffix: .html #指定模板页面名称的后缀#上面的是之前的,下面的就是主要的信息,其中basename指定路径,代表要操作的位置信息#Thymeleaf消息实现国际化多语言,这里绑定资源文件夹下的message文件#假如是xxx.message,那么绑定xxx文件下的message文件(资源文件夹开始的,即资源文件夹里面的xxx文件)messages:basename: message
绑定后,一般需要创建三个文件,message.properties,message_zh_CN.properties,message_en.properties
如果你创建了文件,那么创建的文件名称一般必须是他们的其中一个且名称一样(类型也必须是properties),否则报错
即不是他们的完全名称就会报错
但也要注意:message.properties文件必须创建,即必须有,否则也会报错
上面的报错并不是使得程序停止的报错,而是一种提示,后面会说明
对应的文件信息:
message.properties:
home.tv=哈哈哈
message_zh_CN.properties:
home.tv=哦哦哦
message_en.properties:
home.tv=嘿嘿嘿
当前端的代码是如下时:
<span th:text="#{home.tv}">哈哈哈Hi~ o(* ̄▽ ̄*)ブ</span>
<!--
由于配置的存在,那么其中的home.tv会去对应的路径里找对应的文件
找的文件全名名称是固定的,且会检查,所以说名称要完全一致,否则报错
至此那么他会先得到那个文件的信息呢:优先级是
message_zh_CN.properties > message.properties > message_en.properties
所以他的内容就是"哦哦哦",当删除这个文件(由于message.properties还在,则不会报错),这时就是"哈哈哈"了
但我们发现,message.properties不能删除,那么message_en.properties基本是操作不了的
实际上在特殊的情况下会进行操作,具体可以百度
如果删除掉,报错的信息并不会停止程序,而是打印出来,一般是??home.tv_zh_CN_#Hans??,且显示在前端
所以说这个报错也只是一个提示而已
实际上没有配置或者没有找到(全部删除,也正好删除了message.properties)也会出现这个提示
-->
至此操作成功
链接表达式 @{…}:
链接表达式@{…}一般用于页面跳转或者资源的引入
在Web开发中占据着非常重要的地位,并且使用也非常频繁,示例代码如下:
<a  th:href="@{http://localhost:8080/order/details(orderId=${o.id})}">view</a>
<a  th:href="@{/order/details(orderId=${o.id},pid=${p.id})}">view</a>
上述代码中,链接表达式@{…}分别编写了绝对链接地址和相对链接地址
在有参表达式中,需要按照@{路径(参数名称=参数值,参数名称=参数值…)}的形式编写
同时该参数的值可以使用变量表达式来传递动态参数值(如上面的${o.id})
上面的只是一个示例,来一个通用的示例:
<a th:href="@{http://www.baidu.com}">点击跳转百度</a>
<!--
点击跳转百度,实际上是根据/来操作的
我为什么要这样说呢,假设是/www.baidu.com,那么一定的端口后面的地址
比如是http://localhost:8080/www.baidu.com
如果是//www.baidu.com(注意:这里没有加http:)
那么就是总地址,即就是www.baidu.com(浏览器会默认加上https://)
也就是https://www.baidu.com/
所以说实际上是//的作用,从第一个/或者//开始解释,其他的代表层级(这句话基本适用于所有操作url地址的java代码)
口头说明可能并没有说服力,你实验实验就知道了,自己测试吧
-->
片段表达式 ~{…}:
片段表达式~{…}用来标记一个片段模板,并根据需要移动或传递给其他模板
其中,最常见的用法是使用th:insert或th:replace属性插入片段,示例代码如下:
<div th:insert="~{thymeleafDemo::title}"></div>
上述代码中,使用th:insert属性将title片段模板引用到该标签中
thymeleafDemo为模板名称,Thymeleaf会自动查找"/resources/templates/"目录下的thymeleafDemo模板,title为片段名称
我们也进行测试一下,在对应的templates目录下创建index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div th:fragment="fa"> <!--设置片段名为fa-->jjjj
</div>
</body>
</html>
在对应的login.html加上如下代码:
<div th:insert="~{index::fa}"></div><!--div基本是占一行的-->
<!--得到index模板(也就是index.html)里面片段名为fa的内容(整个div),到这个div里面-->
至此,若数据的确过去了,则操作完毕
我们可以给出一个这样的界面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1,shrink-to-fit=no">
<title>用户登录界面</title>
<link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/login/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<!--  用户登录form表单 -->
<form class="form-signin">
<img class="mb-4" th:src="@{/login/img/login.jpg}" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal">请登录</h1>
<input type="text" class="form-control" th:placeholder="用户名" required="" autofocus="">
<input type="password" class="form-control" th:placeholder="密码" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> 记住我
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" >登录</button>    <p class="mt-5 mb-3 text-muted">© <span th:text="${currentYear}">2019</span>-<span th:text="${currentYear}+1">2020</span></p></form></body>
</html>
通过xmlns:th="http://www.thymeleaf.org"引入了Thymeleaf模板标签
使用"th:href"和"th:src"分别引入了两个外联的样式文件和一个图片
使用"th:text"引入了后台动态传递过来的当前年份currentYear
这些就是总体的一个小界面,在对应的静态文件存在的情况下,一般是如下的显示(我这里是):

当然,这是以前操作的图片,所以年份是不符合的
可以看出,登录页面login.html显示正常,在文件中使用"th:*"相关属性引入的静态文件生效
并且在页面底部动态显示了当前日期2020-2021,而不是文件中的静态数字2019-2020
这进一步说明了Spring Boot与Thymeleaf整合成功,完成了静态资源的引入和动态数据的显示
SpringBoot实战演练:
实战技能补充:lombok
可以使用注解解决对应的get,set,toString,有参构造,无参构造的生成方式,而不用自己生成了
<dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>           <version>1.18.12</version>            <!--只在编译阶段生效(只在编译和测试的时候用),编译使得不会出现错误提示-->            <scope>provided</scope>
</dependency>
需求:实现用户的CRUD功能
创建springboot工程:

可以发现,有对应的三个(加上了Lombok)
实际上也可以加上对应的数据库驱动,只要是你需要的都可以加上(有选项的情况下)
对应生成的Lombok是如下:
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional> <!-- 表示依赖不会传递 --></dependency>
记得将application.properties文件修改成application.yml,一般都会修改(因为他的格式好观察)
User实体类编写:
package com.lagou.thy.bean;import lombok.Data;/****/
@Data
public class User {private Integer id;private String username;private String password;private String birthday;private static final long serialVersionUID = 1L;
}
进行测试(在默认创建的类里面进行测试):
package com.lagou.thy;import com.lagou.thy.bean.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class ThyApplicationTests {@Testvoid contextLoads() {User u = new User();u.setId(1);u.setUsername("好好");System.out.println(u);}}
发现,都进行了操作,也就是说@data注解帮我们生成了对应的get,set,toString的生成方式,而不用自己生成了
当然,既然有对应的User类,那么数据库也一般有对应的表,sql语句如下:
CREATE TABLE USER(
id INT(10) PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20),
PASSWORD VARCHAR(20),
birthday VARCHAR(20)
)INSERT INTO USER VALUES(1,'zhangsan','123','2020-10-10')
-- valus插入单个数据快(一个括号的就是单个数据),value插入多个数据快(多个括号的就是多个数据)
-- 比如INSERT INTO USER VALUES(1,'zhangsan','123','2020-10-10'),就是单个数据
-- INSERT INTO USER VALUES(2,'zhangsan','123','2020-10-10'),(3,'lisi','123','2020-10-10')
-- 就是多个数据
导入对应的依赖:
<dependency>        <!--连接池--><groupId>com.alibaba</groupId>        <artifactId>druid</artifactId>        <version>1.1.3</version> <!--spring boot的druid版本设置好像没有,则这里需要写上版本-->
</dependency>
注意:数据库的驱动也要进行导入,当然对应的依赖如下:
 <dependency><!--数据库驱动--><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><!--使用spring boot的版本--></dependency>
这里我们通过前面的Free Mybatis plugin插件来生成代码:
具体的操作看前面即可,对User表来操作,当然生成的记得自己进行检查
为了后面的开发,对应的启动类,先加上接口的扫描:
package com.lagou.thy;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;@SpringBootApplication
@MapperScan("com.lagou") //需要mybatis的依赖,以及mybatis和spring整合的依赖,以及jdbc的依赖
//但由于需要与spring boot整合(不需要整合的一般不用加对应的spring boot依赖,如驱动依赖)
//即需要直接导入对应的spring boot整合mybatis的依赖(包括了上面说的所有依赖,即不需要导入上面的依赖了)
//如mybatis-spring-boot-starter,里面包含了对应的依赖(一个总体)
public class ThyApplication {public static void main(String[] args) {SpringApplication.run(ThyApplication.class, args);}}
我的具体目录如下:

会发现,对应的mapper并不是dao包,这是我故意的,增强你的理解能力,因为并不是所有的项目都是对应的
如mapper对应UserMapper
对应的application.yml文件如下:
##服务器配置
server:port: 8090#默认情况下就是/,所以下面的可以不加servlet:context-path: /
##数据源配置
spring:datasource:name: druidtype: com.alibaba.druid.pool.DruidDataSource#上面两个配置使得使用连接池操作,而不是单独的连接url: jdbc:mysql://localhost:3306/springbootdata?characterEncoding=utf-8&serverTimezone=UTCusername: rootpassword: 123456
#整合mybatis
mybatis:#声明Mybatis映射文件所在的位置mapper-locations: classpath:mapper/*Dao.xml
接着在thy包下创建service包,并创建UserService接口:
package com.lagou.thy.service;import com.lagou.thy.bean.User;import java.util.List;/****/
public interface UserService {//查询所有List<User> queryAll();//通过id查询User findById(Integer id);//新增//返回的数据,可以设置为void,因为得到的返回响应条数一般并不需要void insert(User user);//通过id删除void deleteById(Integer id);//修改void update(User user);}
补充生成的UserDao接口:
//查询所有
List<User> queryAll();
一般会爆红,因为没有对应的配置,点击他自动生成xml的一些配置模板(注意:sql需要自己补充),记得进行检查
创建实现类:
package com.lagou.thy.service.impl;import com.lagou.thy.bean.User;
import com.lagou.thy.dao.UserDao;
import com.lagou.thy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/****/
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Overridepublic List<User> queryAll() {List<User> users = userDao.queryAll();System.out.println(1);return users;}@Overridepublic User findById(Integer id) {User user = userDao.selectByPrimaryKey(id);return user;}@Overridepublic void insert(User user) {//userDao.insert(user); //将除了id的所有都拼sql语句userDao.insertSelective(user); //将不为空(null)的列才拼sql语句//但他们都是添加语句,一般我们优先使用后面的//首先我们判断是是null而不是null字符串(因为可以加入)//那么若sql中的字段有设置不为空的,即会出现添加失败,一般会使得报错//所以一般使用insertSelective方法来操作}@Overridepublic void deleteById(Integer id) {userDao.deleteByPrimaryKey(id);}@Overridepublic void update(User user) {//这里与添加的操作一样,也是使用updateByPrimaryKeySelective方法//userDao.updateByPrimaryKey(user);userDao.updateByPrimaryKeySelective(user);}
}
在对应的thy包下,创建controller包,并创建UserController类:
package com.lagou.thy.controller;import com.lagou.thy.bean.User;
import com.lagou.thy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/****/
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;/*restful风格(格式)进行操作(该风格,即格式可以到68章博客进行查看)查询:GET(get)新增:POST(post)更新:PUT(put)删除:DELETE(delete)*///查询所有@GetMapping("/query")public List<User> queryAll(){List<User> users = userService.queryAll();return users;}//通过id查询@GetMapping("/query/{id}")//当然@PathVariable("id")注解,也可以不操作指定id//让对应变量的自动匹配,这里可以不加(因为刚好一样),但无论加不加,只要对应的没有找到,就会报错//如@PathVariable Integer i,或者@PathVariable("o") Integer id,都会报错//从而该方法就不会执行,底层返回错误信息public User queryById(@PathVariable Integer id){return userService.findById(id);}//通过id删除@DeleteMapping("/delete/{id}")public String delete(@PathVariable Integer id){userService.deleteById(id);return "删除成功";}//新增@PostMapping("insert")public String insert(User user){userService.insert(user);return "新增成功";}//修改@PutMapping("/update")public String update(User user){userService.update(user);return "修改成功";}}
对应的测试:
查询所有:

通过id查询:

通过id删除:

新增:

修改(注意id要存在,虽然不存在也可,只是数据库的数据没有变化):

至此都操作成功,记得对应的请求方式需要与后端要一致的(因为后端代码的作用,否则则会提示错误信息)
之所以使用postman来进行测试,是因为浏览器的直接测试,基本只能操作get
Spring Boot项目部署:
需求:将Spring Boot项目使用maven指令打成jar包并运行测试
分析:
需要添加打包组件将项目中的资源、配置、依赖包等等打到一个jar包中,可以使用maven的package
部署:java -jar 包名
步骤实现:
添加打包组件
<build>        <plugins>           <!--
打jar包时如果不配置该插件,打出来的jar包没有清单文件,那么启动时,会提示没有
那么什么是清单文件呢,我们可以看下面的图片中知道,有一个thy-0.0.1-SNAPSHOT.jar.original文件
该文件就是清单文件,而我们删除这个配置时,该文件不会生成
只有当对应的jar包和他的清单文件在同一个目录时,才可启动(这里要注意,若不相信,那么可以自己进行测试)
注意:这里说的是他的清单文件,若是其他的清单文件那么基本不可以,因为jar包和清单文件是一一对应的
-->     <!--war包打包并不需要依赖配置,直接打包即可--><plugin>              <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-maven-plugin</artifactId>      </plugin>       </plugins>
</build>
部署运行(与前面的dubbo的运行类似,实际上就是对应的可自动运行的类,即可运行的jar包):
点击如下:

点击右边的package就会生成左边的thy-0.0.1-SNAPSHOT.jar,运行方式如下:
//使用java -jar 包名
//这里就是java -jar thy-0.0.1-SNAPSHOT.jar
//启动就相当于执行了对应的启动类
//这时是自动的了,实际上也可以说是手动,因为我们是进行执行命令的
//但对应的启动类却不是我们来启动的,所有也一般称为自动的启动
进入cmd,找到对应的目录,执行如下:

等待他操作完毕(也要注意端口占用,否则一般会直接的停止),这时排除后,再次进行启动
至此再次测试访问postman的对应url,发现,的确进行了操作(返回了数据)

88-Spring Boot详解相关推荐

  1. 微服务开发的入门级框架Spring Boot详解:注解

    2019独角兽企业重金招聘Python工程师标准>>> 通过前两章节的介绍,大家应该对Spring Boot有了些许的认识,也感觉到了这个框架带来的便利,下面我将讲解SpringBo ...

  2. “史上最全”Spring Boot详解!java程序员细节到极致的一次,魔鬼

    这本书的灵感 当时公司的技术栈全面转入Spring Boot体系,源于本书作者在公司的一次分享会上的分享.当时作者用了不到10分钟就使用Spring Boot轻松制作了一个功能完整的数据增加.删除.修 ...

  3. Spring IoC详解

    Spring IoC详解 原文地址:Spring IoC详解 写在最前 本文将主要写Spring最核心的部分,为什么写这篇的原因也是因为在刚开始学习Spring的时候,学得太粗糙了.感觉学了个皮毛,从 ...

  4. Spring AOP详解(转载)所需要的包

    上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...

  5. Spring JDBC详解

    <Spring JDBC详解> 本文旨在讲述Spring JDBC模块的用法.Spring JDBC模块是Spring框架的基础模块之一. 一.概述 在Spring JDBC模块中,所有的 ...

  6. Spring 体系结构详解

    Spring 体系结构详解 核心容器(Core Container) Core和Beans模块提供了Spring最基础的功能,提供IOC和依赖注入特性.这里的基础概念是BeanFactory,它提供对 ...

  7. [转载]Spring配置文件详解一:

    2019独角兽企业重金招聘Python工程师标准>>> 原文地址:Spring配置文件详解一:<context:annotation-config/>与<conte ...

  8. struts2+hibernate+spring配置详解

    #struts2+hibernate+spring配置详解 struts2+hibernate+spring配置详解 哎 ,当初一个人做好难,现在终于弄好了,希望自学这个的能少走些弯路. 以下是自己配 ...

  9. spring注解详解与用法(总览)

    这篇文章收集了我写的所有的spring注解的详细说明与用法,点击可以跳转到对应文章,此文章会不断更新 spring注解详解与用法(1)最基础也是最常见的如下所示,详情点击这里 @Controller/ ...

最新文章

  1. 保存oracle数据之后乱码,Oracle保存中文数据和读取数据页面显示乱码解决方案
  2. vue项目运行启动方法(从github上下载了一个前端项目进行运行)
  3. FreeRTOS 之五 动态内存管理(heap_1.c)详解
  4. inspinia中文管理后台_Bootstrap优秀模板-INSPINIA.2.9.2
  5. 11.1.2 DOM
  6. G盘文件系统损坏要如何恢复数据
  7. ArchSummit微课堂|蘑菇街DevOps实践及心路历程分享
  8. yum install php-pecl-mongo,pecl安装php mongodb扩展
  9. apache iotdb_高性能轻体量物联网数据库Apache IoTDB
  10. 学习一下 PDF417 条码
  11. matlab好看的字体,[转载]最合适写代码的字体
  12. 符号函数的作用及Matlab中的基本用法
  13. JAVA 实现汉字五行笔画查询
  14. 浅谈认识商业智能过程中遇到的困难
  15. 为小米4与小米3 Mi3 Mi4编译Cyanogenmod 12.1与13.0 (CM12与CM13) 的步骤以及错误解决
  16. 大学学嵌入式技术的优势
  17. 【七夕特效】 -- 满屏爱心
  18. 重返帝国T0阵容搭配
  19. 篮球比赛计时计分系统
  20. 【论文翻译】(UAI 2018)使用感知预测网络进行潜在物理属性的无监督学习

热门文章

  1. R语言统计入门第六章——回归与相关性
  2. android studio安装apk时,vivo 部分手机出现 解析软件包错误
  3. prev_permutation 函数
  4. 【Flutter】如何完成一个透明沉浸式状态栏
  5. 大数据告诉你:2019年该学习什么技术
  6. CAD中如何查看要素高程
  7. word转换为html代码,如何将Word转换为网页html格式的方法(附代码清理方法)
  8. 推荐给中学生的数学课外书:《写给全人类的数学魔法书》
  9. android手机调用linux ping命令
  10. PhotoShop 安装PS时提示安装程序检测到计算机重新启动操作可能处于挂起状态,建议您退出安装...