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>
< parent>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-parent</ artifactId>
< version> 2.7.2</ version>
</ parent>
< dependencies>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-web</ artifactId>
</ dependency>
</ dependencies>
< 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 ;
@SpringBootApplication
public class SpringBootDome1Application {
public static void main ( String [ ] args) {
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>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-test</ artifactId>
< scope> test</ scope>
</ dependency>
< dependency>
< 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 ( SpringRunner . class )
@SpringBootTest
class Springbootdome2ApplicationTests {
@Autowired
private HelloController helloController;
@Test
public void contextLoads ( ) {
String s = helloController. helloBoot ( ) ;
System . out. println ( s) ;
}
}
启动后,执行方法,若返回正确的数据,则操作成功
注意:测试的类也要在启动类对应的包或者包下,即对应的,否则是操作不了,即会报错(初始化报错)
所以一般我们快速的创建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 boot自身的处理,其他的如单纯的spring可能还是需要指定)
#其他的基本都可以有空格(且空格无影响,相当于没有什么,基本不会参与到数值里面去)
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>
</ dependency>
还需要如下的包:
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-jdbc</ artifactId>
< version> 2.7.1</ version>
</ dependency>
至此我们可以操作如下:
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 哈哈哈" ;
}
@Autowired
private JdbcTemplate jdbcTemplate;
@RequestMapping ( "/jdbc" )
public String jdbc ( ) {
return jdbcTemplate. toString ( ) ;
}
}
进行访问,若有对应的数据,那么注入完成,即操作完成
接下来,通过一个案例对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;
@Override
public 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
@ConfigurationProperties ( prefix = "person" )
public class Person {
private int id;
private String name;
private List hobby;
private String [ ] family;
private Map map;
private Pet pet;
@Override
public 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,并进行输出测试
@Autowired
private Person person;
@Test
public void configurationTest ( ) {
System . out. println ( person) ;
}
若返回数据则代表注入成功,即操作成功
但是这里可能会出现乱码问题,因为properties对应操作特殊字符时(如中文)时
不会使用设置的编码,而是使用ISO 8859-1来解码,这就导致了无论你怎么设置编码(在能操作中文的编码的情况下)
对应的基本都是乱码,因为我们编码的一般不可能是ISO 8859-1,因为要操作中文,他基本不能解析该中文
那么对应编码也是有问题的(就算是同样的该编码,可能也是乱码,因为他是基本不操作中文的,否则不会)
那么如何不让他进行默认的ISO 8859-1来解码呢,点击如下:
只要右边的勾勾,勾上了即可,代表自动匹配操作我们的格式,因为对应的编码一般从全局得到,左边的一般是显示的文件编码
好像并不会参与程序里面的编码,只是用来看的,最好其他的配置都是UTF-8,以防万一
当然,除了右边的非ascii的自动配置,其他的都只是显示,也就是说选择他基本可以操作内部中编码的处理了,还有上面虽然说是编码,本质上除了这个非ascii,他们只是操作一些显示,具体需要框架中或者idea中内部的编码处理(大多数情况下,都是utf8的)
但是在一些配置文件(如这里就需要选择),其内部可能是默认操作ISO的,所以我们通常需要选择非ascii来自动匹配,这里了解即可(也就是说:Transparent native-to-ascii conversion支持在使用资源文件(例如 .properties文件)时处理非ASCII字符,例如中文)
至此我们可以在对应的HelloController类里加上如下代码:
@Autowired
private 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格式配置文件看起来更简洁一些,他们的区别主要在于基本上springboot的配置他们都有联系,但是并非都有编写,所以大多数情况下,可以通过properties来得到yml的编写,但是有些只有yml才可以或者只有properties才可以,相当于整合的依赖中,都存在他们的配置,只不过有些只操作yml,有些只操作properties,现在我们基本考虑yml,所以后面也基本以这个为主的(yaml与yml是一样的,解析也是一样,当然,可能某些只会看后缀名称,但是这里非常少,所以这里不考虑)
YAML文件的扩展名可以使用.yml或者.yaml
application.yml文件使用 "key:(空格) value"格式配置属性,使用缩进控制层级关系
SpringBoot的三种配置文件是可以共存的,也就是说,可以写这三个配置文件,都会进行读取:
< includes>
< include> **/application*.yml</ include>
< include> **/application*.yaml</ include>
< include> **/application*.properties</ include>
</ includes>
这里,针对不同数据类型的属性值,介绍一下YAML
value值为普通数据类型(例如数字、字符串、布尔等)
当YAML配置文件中配置的属性值为普通数据类型时,可以直接配置对应的属性值
同时对于字符串类型的属性值,不需要额外添加引号,示例代码如下
server :
port : 8080
servlet :
context-path : /hello
value值为数组和单列集合:
当YAML配置文件中配置的属性值为数组或单列集合类型时,主要有两种书写方式:缩进式写法和行内式写法
其中,缩进式写法还有两种表示形式,示例代码如下
person :
hobby :
- play
- read
- sleep
person :
hobby :
- play
- read
- sleep
person :
hobby :
- play
- read
- sleep
person :
hobby :
- play
- read
- sleep
person :
hobby :
- play
- read
- sleep
或者使用如下示例形式:
person :
hobby :
play,
read,
sleep
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 :
id : 1
name : 王二麻子
family :
- 妻
- 妾
hobby :
- play
- read
- sleep
map :
k1 : value1
k2 : value2
pet :
type : 狗
name : 哈士奇
再次执行测试:
若返回数据,则操作成功
好像这时候更加的操作@Value()注解都只能操作单独的类型了(数组,集合,类好像都不能操作),你可以百度进行查找对应操作
大概是对应的@ConfigurationProperties(prefix = “person”)注解中有对应的解析方式,看到上面的时候,存在使得移除了吧
最后注意一下:使用spring boot时,对应的url访问不能出现多余的/,也就是说
//斜杠的访问不会被后端(以前说是浏览器的作用,实际上是因为浏览器访问后端造成的)解析成一个/
这时就会出现访问不了,或者网页不存在(因为Spring Boot没有这些操作,而其他的如单纯的mvc就会有的,所以可能是因为springboot整合mvc时,会提前判断,并进行更加严格的考虑)
配置文件属性值的注入:
使用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;
@Override
public String toString ( ) {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}' ;
}
}
Student类使用@Value注解将配置文件的属性值读取和注入
从上述示例代码可以看出
使用@Value注解方式需要对每一个属性注入设置(相当于直接的赋值,反射可以赋值private,在其他地方)
同时又免去了属性的setXX()方法
再次打开测试类进行测试:
@Autowired
private Student student;
@Test public void studentTest ( ) {
System . out. println ( student) ;
}
若返回数据,则操作成功
可以看出,测试方法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" )
@ConfigurationProperties ( prefix = "test" )
public class MyProperties {
private int id;
private String name;
@Override
public 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开头的属性值注入到该配置类属性中
进行测试:
@Autowired
private MyProperties myProperties;
@Test
public void myPropertiesTest ( ) {
System . out. println ( myProperties) ;
}
若有对应的数据,则操作成功
注意:在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
public MyService myService ( ) {
return new MyService ( ) ;
}
}
MyConfig是@Configuration注解声明的配置类(类似于声明了一个XML配置文件)
该配置类会被Spring Boot自动扫描识别,使用@Bean注解的myService()方法
其返回值对象会作为组件添加到了Spring容器中(类似于XML配置文件中的标签配置),并且该组件的id默认是方法名myService
测试类:
@Autowired
private MyService myService;
@Autowired
private MyConfig myConfig;
@Test
public void iocTest ( ) {
System . out. println ( myService) ;
System . out. println ( myConfig) ;
}
若返回数据,则代表注入成功,当然也可以操作如下:
@Autowired
private ApplicationContext applicationContext;
@Test
public void Test ( ) {
System . out. println ( applicationContext. getBean ( "myService2" ) ) ;
System . out. println ( applicationContext. containsBean ( "myService2" ) ) ;
}
上述代码中,先通过@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/>
</ 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底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理
例如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>
< dependency>
< groupId> org.springframework</ groupId>
< artifactId> spring-web</ artifactId>
< version> 5.3.22</ version>
< scope> compile</ scope>
</ dependency>
< dependency>
< 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 } )
@Retention ( RetentionPolicy . RUNTIME )
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan (
excludeFilters = { @Filter (
type = FilterType . CUSTOM ,
classes = { TypeExcludeFilter . class }
) , @Filter (
type = FilterType . CUSTOM ,
classes = { AutoConfigurationExcludeFilter . class }
) }
)
public @interface SpringBootApplication {
@AliasFor (
annotation = EnableAutoConfiguration . class
)
Class < ? > [ ] exclude ( ) default { } ;
@AliasFor (
annotation = EnableAutoConfiguration . class
)
String [ ] excludeName ( ) default { } ;
@AliasFor (
annotation = ComponentScan . class ,
attribute = "basePackages"
)
String [ ] scanBasePackages ( ) default { } ;
@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配置文件的注解表现形式),并可以被组件扫描器扫描(如:ComponentScan)
由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类
只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已,这样会使得该类可以被获取调用(底层获取执行)
从而执行main方法
虽然我们也可以再次进行获取,但也要注意,同一个类里面,多个不同的变量(通常考虑同类型),基本是能注入同一个对象的
@EnableAutoConfiguration注解:
@EnableAutoConfiguration:开启自动配置功能,以前由我们需要配置的东西,现在由SpringBoot帮我们自动配置
这个注解就是Springboot能实现自动配置的关键
同样,查看该注解内部查看源码信息,核心代码具体如下 :
@Target ( { ElementType . TYPE } )
@Retention ( RetentionPolicy . RUNTIME )
@Documented
@Inherited
@AutoConfigurationPackage
@Import ( { AutoConfigurationImportSelector . class } )
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration" ;
Class < ? > [ ] exclude ( ) default { } ;
String [ ] excludeName ( ) default { } ;
}
可以发现它是一个组合注解, Spring 中有很多以Enable开头的注解
其作用就是借助@Import(主要用来导入配置类,不是配置类也可以(有些对应版本必须要配置类,但现在基本可以不是配置类了,但因为这个存在,如果对方存在其他扫描的,那么会进行处理(通常这个为主,当然,可能会出现错误,但是一般没有,因为基本不会出现,即他们是覆盖的关系),即他也看成一个bean了,即通过@Import注解导入的类可以成为Spring中的bean,并且由于是导入,所以就算他不是配置类,也可以读取到@Bean))来收集并注册特定场景相关的Bean,并加载到IOC容器
通常来说,如果导入的不是配置类,一般是因为实现了 ImportSelector 或 ImportBeanDefinitionRegistrar 接口的类,这些接口允许你在运行时动态地注册 Bean 定义
@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器
下面,对这两个核心注解@AutoConfigurationPackage和@Import分别进行讲解:
@AutoConfigurationPackage注解:
查看@AutoConfigurationPackage注解内部源码信息,核心代码具体如下:
@Target ( { ElementType . TYPE } )
@Retention ( RetentionPolicy . RUNTIME )
@Documented
@Inherited
@Import ( { Registrar . class } )
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 ] ) ) ;
}
public Set < Object > determineImports ( AnnotationMetadata metadata) {
return Collections . singleton ( new AutoConfigurationPackages. PackageImports ( metadata) ) ;
}
}
从上述源码可以看出,在Registrar类中有一个registerBeanDefinitions()方法
使用Debug模式启动项目(记得指定对应的位置,AutoConfigurationPackages.register方法)
查看对应的metadata(一般包括注解操作的信息,一般指向对应总注解对应的类,这里是Springbootdome2Application类):
在这之前,我们需要说明一下:
扫描,配置类等等的说明:首先扫描通常是注解的操作,也就是说配置类的注解也属于扫描范围,当然,配置类存在三种,一个是注解,一个是导入时实现接口的操作(通常与文件一起,所以不考虑说明他),一个是文件的读取操作,他们都是考虑创建bean的
也就是说对应的元数据是启动类名称
我们发现,通过注解,的确得到了当前类的对应的包com.lagou(实际上是结合了@ComponentScan注解,而得到的地址信息,扫描,因为启动类是配置类,要么扫描导入,要么进行扫描)
也就是说,@AutoConfigurationPackage注解的主要作用就是将主程序类所在包及所有子包下的组件扫描到spring容器中(这里注意了,他只是将信息放入对应的列表中)
因为他操作了@Import注解,一般是将实例放入到ioc容器中的操作或者一些其他的方法处理(上面的)
该注解在这里一般会操作执行参数类的方法,好像是固定的几个
如selectImports方法包括内部类的,可能也有registerBeanDefinitions方法
因此 在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置
然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫描
@Import({AutoConfigurationImportSelector.class})注解:
将AutoConfigurationImportSelector这个类导入到Spring容器中
AutoConfigurationImportSelector可以帮助Springboot应用
将所有符合条件的@Configuration配置(配置类)都加载到当前SpringBoot创建并使用的IOC容器(ApplicationContext)中
如果说@AutoConfigurationPackage注解是得到扫描的实例信息(可能包括配置类),但却不能操作其他依赖的配置类和扫描信息(指其他引入的依赖)
那么这个@Import({AutoConfigurationImportSelector.class})注解是操作配置类或者扫描得到实例,包括完成@AutoConfigurationPackage注解定义信息后续的自动扫描处理(当然,这个处理可能由其他操作来完成,或者spring自身上下文存在的处理),但一般是需要先进行扫描,他们一起,使得扫描得到实例,好像spring中扫描时,他们是一起操作的,而不是分开,spring boot却是分开,但总体是一起
他们两个都需要操作完才会真正的启动,简单来说第一个注解定义扫描信息,另外一个是操作自动的配置,然后处理文件的配置类创建bean(一般没有扫描,所以前面是"或")
继续研究AutoConfigurationImportSelector这个类
通过源码分析这个类中是通过selectImports这个方法告诉springboot都需要导入那些组件:
public String [ ] selectImports ( AnnotationMetadata annotationMetadata) {
if ( ! this . isEnabled ( annotationMetadata) ) {
return NO_IMPORTS ;
} else {
AutoConfigurationImportSelector. AutoConfigurationEntry autoConfigurationEntry =
this . getAutoConfigurationEntry ( annotationMetadata) ;
return StringUtils . toStringArray ( autoConfigurationEntry. getConfigurations ( ) ) ;
}
}
一般的,他应该有这个方法(具体怎么得到可以百度):
this . autoConfigurationMetadata = AutoConfigurationMetadataLoader . loadMetadata ( this . beanClassLoader) ;
在另外一个selectImports方法里面的this.getAutoConfigurationMetadata()方法里面,他们基本是一起操作的
但有些版本,大概是低版本,一般全部在同一个方法里面,即String[] selectImports方法里面,如:
深入研究loadMetadata方法(无论新版本还是从前版本基本都是一样的):
static AutoConfigurationMetadata loadMetadata ( ClassLoader classLoader) {
return loadMetadata ( classLoader, "META-INF/spring-autoconfigure-metadata.properties" ) ;
}
static AutoConfigurationMetadata loadMetadata ( ClassLoader classLoader, String path) {
try {
Enumeration < URL> urls = classLoader !=
null ? classLoader. getResources ( path) : ClassLoader . getSystemResources ( path) ;
Properties properties = new Properties ( ) ;
while ( urls. hasMoreElements ( ) ) {
properties. putAll ( PropertiesLoaderUtils . loadProperties (
new UrlResource ( ( URL ) urls. nextElement ( ) ) ) ) ;
}
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容器中注入我们自动配置的类需要满足=号后面的条件
#具体条件(条件有很多种,不同的注解是不同的意思,比如@ConditionalOnBean:判断某个特定的 bean 是否存在,如果存在,则注册当前的 bean)是
#当该注解里面出现了后面的org.springframework.amqp.rabbit.annotation.EnableRabbit(EnableRabbit这个类时,因为约定,所以存在某个地方统一获取的)
#就进行该RabbitAnnotationDrivenConfiguration类的自动注入
#自动注入:可以说成是自动的创建实例,放在ioc容器里面(注意:通常只是放入列表中,具体由spring初始化完成)
#即使得可以被注入得到,所以我们导入对应的依赖,spring boot会帮我们生成实例,就是这样的原因,但并不是所有依赖,因为该文件的内容是有限的,这是肯定的
#其他的基本都是这样的说明
#无论是自动配置还是扫描,都是单纯的操作注册(前面或者后面的内容可能说自动配置操作了创建实例,只是结合了总体来说而已,具体只要spring没有初始化,都只是保存对应的包信息,也就是注册,所以后面或者前面说明放入ioc容器本质是结合总体来说的,需要注意这一点)
至此,上面的操作总得来说是得到所有的自动配置类及其需要的对应条件,如果没有,说明不需要条件,自然会进行注入的
至此该方法介绍完毕,接下来我们接着看selectImports方法里面的getAutoConfigurationEntry
AutoConfigurationImportSelector类 getAutoConfigurationEntry方法:
protected AutoConfigurationImportSelector. AutoConfigurationEntry
getAutoConfigurationEntry ( AnnotationMetadata annotationMetadata) {
if ( ! this . isEnabled ( annotationMetadata) ) {
return EMPTY_ENTRY ;
} else {
AnnotationAttributes attributes = this . getAttributes ( annotationMetadata) ;
List < String > configurations = this . getCandidateConfigurations ( annotationMetadata,
attributes) ;
configurations = this . removeDuplicates ( configurations) ;
Set < String > exclusions = this . getExclusions ( annotationMetadata, attributes) ;
this . checkExcludedClasses ( configurations, exclusions) ;
configurations. removeAll ( exclusions) ;
configurations = this . getConfigurationClassFilter ( ) . filter ( configurations) ;
this . fireAutoConfigurationImportEvents ( configurations, exclusions) ;
return new AutoConfigurationImportSelector. AutoConfigurationEntry ( configurations,
exclusions) ;
}
}
深入getCandidateConfigurations方法(上面的:获取默认支持的自动配置类列表这个注释):
protected List < String > getCandidateConfigurations ( AnnotationMetadata metadata, AnnotationAttributes
attributes) {
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 ( ) ) ;
}
我们再次点开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 {
Enumeration urls = classLoader. getResources ( "META-INF/spring.factories" ) ;
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中的扫描基本也是如此,但他好像是一起的)
这里就需要进行一下总结了:
@Import ( { Registrar . class } )
@Import ( { AutoConfigurationImportSelector . class } )
以刚刚的项目为例,举个例子:
在项目中加入了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注解也是扫描,但是主要是排除
至此@SpringBootApplication 的注解的功能就分析差不多了, 简单来说就是 3 个注解的组合注解:
那么注解怎么操作的呢,自然是由于启动时考虑的初始化呗,了解即可,以后说明源码时会知道的
实际上,无论是自动配置还是扫描,都是单纯的操作注册,具体需要spring初始化时统一进行的,以后说明底层原理的源码时会说明的
所以简单来说就是这样的:
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;
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基础入门' , '从入门到精通讲解...' ) ;
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/>
</ 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>
< dependency>
< groupId> org.mybatis.spring.boot</ groupId>
< artifactId> mybatis-spring-boot-starter</ artifactId>
< version> 2.2.2</ version>
</ dependency>
< dependency>
< groupId> mysql</ groupId>
< artifactId> mysql-connector-java</ artifactId>
< scope> runtime</ scope>
</ 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;
@Override
public 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;
@Override
public 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),因为好观察,这个配置文件中进行数据库连接配置
spring :
datasource :
url : jdbc: mysql: //localhost: 3306/springbootdata? serverTimezone=UTC&characterEncoding=UTF- 8
username : root
password : 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" )
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 {
@Autowired
private CommentMapper commentMapper;
@Test
void 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
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
mapper-locations : classpath: mapper/*.xml
type-aliases-package : com.lagou.bootmybatis.pojo
mybatis :
configuration :
map-underscore-to-camel-case : true
mapper-locations : classpath: mapper/*.xml
type-aliases-package : com.lagou.bootmybatis.pojo
mybatis :
configuration :
map-underscore-to-camel-case : true
mapper-locations : classpath: mapper/*.xml
type-aliases-package : com.lagou.bootmybatis.pojo
编写单元测试进行接口方法测试:
@Autowired
private ArticleMapper articleMapper;
@Test
void findArticleMapperById ( ) {
Article article = articleMapper. selectByPrimaryKey ( 1 ) ;
System . out. println ( article) ;
}
至此若返回数据,则操作完毕
总体来说,spring boot封装了一些自动的配置,但有些并没有
但封装的这些,却大大的提高了我们的开发操作(虽然运行可能会更加慢些,但基本是启动的运行,而不是启动后的)
Spring Boot整合Redis
添加Redis依赖包:
在项目的pom.xml中添加如下:
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-data-redis</ artifactId>
</ dependency>
配置Redis数据库连接:
在application.properties中配置redis数据库连接信息,如下(总体的配置):
spring :
datasource :
url : jdbc: mysql: //localhost: 3306/springbootdata? serverTimezone=UTC&characterEncoding=UTF- 8
username : root
password : 123456
redis :
host : 192.168.164.128
port : 6379
mybatis :
configuration :
map-underscore-to-camel-case : true
mapper-locations : classpath: mapper/*.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 {
@Autowired
private RedisTemplate redisTemplate;
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 {
@Autowired
private CommentMapper commentMapper;
@Test
void contextLoads ( ) {
Comment byId = commentMapper. findById ( 1 ) ;
System . out. println ( byId) ;
}
@Autowired
private ArticleMapper articleMapper;
@Test
void findArticleMapperById ( ) {
Article article = articleMapper. selectByPrimaryKey ( 1 ) ;
System . out. println ( article) ;
}
@Autowired
private RedisUtils redisUtils;
@Test
public void writeRedis ( ) {
boolean set = redisUtils. set ( "1" , articleMapper. selectByPrimaryKey ( 1 ) ) ;
System . out. println ( set) ;
}
@Test
public void readRedis ( ) {
Article article = ( Article ) redisUtils. get ( "1" ) ;
System . out. println ( article) ;
}
}
对应的redis配置添加:
spring :
datasource :
url : jdbc: mysql: //localhost: 3306/springbootdata? serverTimezone=UTC&characterEncoding=UTF- 8
username : root
password : 123456
redis :
host : 192.168.164.128
port : 6379
jedis :
pool :
max-active : 18
max-wait : 3000
max-idle : 20
min-idle : 2
timeout : 3000
至此,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" >
< 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) ;
如果当前上下文中存在title变量并且程序已经启动,当前P标签中的默认文本内容将会被title变量的值所替换
从而达到模板引擎页面数据动态替换的效果
同时,Thymeleaf为变量所在域提供了一些内置对象,具体如下所示
结合上述内置对象的说明,假设要在Thymeleaf模板引擎页面中动态获取当前国家信息,可以使用#locale内置对象,示例代码如下
The locale country is: < span th: text= " ${#locale.country}" > US</ span>
至此我们可以测试一下:
Thymeleaf模板基本配置
首先 在Spring Boot项目中使用Thymeleaf模板,首先必须保证引入Thymeleaf依赖,示例代码如下:
< dependency>
< 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(没有自行在resources目录中创建)里面创建index.js文件:
function sum ( a, b ) {
return a+ b;
}
直接访问http://localhost:8080/index.js即可,即可以得到该信息
因为默认加上对应的三个子目录进行测试,如这里默认加上static,所以不要加上其他路径,否则一般会找不到文件,使得访问不了
即浏览器一般显示没有对应的网页信息
完成数据的页面展示:
创建Spring Boot项目,引入Thymeleaf依赖 :
当然对应的web也最好加上,操作页面
编写配置文件:
打开application.properties(修改成对应application.yaml)的全局配置文件
在该文件中对Thymeleaf模板页面的数据缓存进行设置
spring :
thymeleaf :
cache : false
encoding : UTF- 8
mode : HTML5
prefix : classpath: /templates/
suffix : .html
使用"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 {
@RequestMapping ( "/toLoginPage" )
public String toLoginPage ( Model model) {
model. addAttribute ( "currentYear" , 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>
< span th: text= " ${currentYear}" > 2019</ span> < br>
< span th: text= " ${currentYearr}" > 2019</ span> < br>
9
</ 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;
}
@Override
public 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模板页面国际化内容的动态替换和展示
使用消息表达式#{…}进行国际化设置时,还需要提供一些国际化配置文件
我们也进行测试一下:
在对应的配置文件中加上如下配置:
spring :
thymeleaf :
cache : false
encoding : UTF- 8
mode : HTML5
prefix : classpath: /templates/
suffix : .html
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>
至此操作成功
链接表达式 @{…}:
链接表达式@{…}一般用于页面跳转或者资源的引入
在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>
片段表达式 ~{…}:
片段表达式~{…}用来标记一个片段模板,并根据需要移动或传递给其他模板
其中,最常见的用法是使用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" >
jjjj
</ div>
</ body>
</ html>
在对应的login.html加上如下代码:
< div th: insert= " ~{index::fa}" > </ 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 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 {
@Test
void 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' )
导入对应的依赖:
< dependency>
< groupId> com.alibaba</ groupId>
< artifactId> druid</ artifactId>
< version> 1.1.3</ version>
</ dependency>
注意:数据库的驱动也要进行导入,当然对应的依赖如下:
< dependency>
< groupId> mysql</ groupId>
< artifactId> mysql-connector-java</ artifactId>
</ 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" )
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 : druid
type : com.alibaba.druid.pool.DruidDataSource
url : jdbc: mysql: //localhost: 3306/springbootdata? characterEncoding=utf- 8&serverTimezone=UTC
username : root
password : 123456
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 ( ) ;
User findById ( Integer id) ;
void insert ( User user) ;
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 {
@Autowired
private UserDao userDao;
@Override
public List < User > queryAll ( ) {
List < User > users = userDao. queryAll ( ) ;
System . out. println ( 1 ) ;
return users;
}
@Override
public User findById ( Integer id) {
User user = userDao. selectByPrimaryKey ( id) ;
return user;
}
@Override
public void insert ( User user) {
userDao. insertSelective ( user) ;
}
@Override
public void deleteById ( Integer id) {
userDao. deleteByPrimaryKey ( id) ;
}
@Override
public void update ( User 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 {
@Autowired
private UserService userService;
@GetMapping ( "/query" )
public List < User > queryAll ( ) {
List < User > users = userService. queryAll ( ) ;
return users;
}
@GetMapping ( "/query/{id}" )
public User queryById ( @PathVariable Integer id) {
return userService. findById ( 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>
< plugin>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-maven-plugin</ artifactId>
</ plugin>
</ plugins>
</ build>
部署运行(与前面的dubbo的运行类似,实际上就是对应的可自动运行的类,即可运行的jar包):
点击如下:
点击右边的package就会生成左边的thy-0.0.1-SNAPSHOT.jar,运行方式如下:
进入cmd,找到对应的目录,执行如下:
等待他操作完毕(也要注意端口占用,否则一般会直接的停止),这时排除后,再次进行启动
至此再次测试访问postman的对应url,发现,的确进行了操作(返回了数据)
注意:jar包中,通常会内置tomcat的,特别的在SpringBoot中都会有,但是他们的内置通常是指在依赖项中,如:
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-web</ artifactId>
</ dependency>
也就是说,内置的 Tomcat 或其他 Servlet 容器通常作为 Maven 或 Gradle 依赖项的一部分存在,这些依赖项会在项目的构建配置文件中指定,比如 pom.xml(对于 Maven)或 build.gradle(对于 Gradle)
具体的内置的 Tomcat 或其他 Servlet 容器并不会直接修改你的代码,它们提供了一个容器环境,用于运行你的应用程序,并负责处理 HTTP 请求和响应,当你启动 Spring Boot 应用时,内置的 Tomcat 会加载你的应用程序并监听指定的端口,等待来自客户端的请求
gradle可以选择到109章博客学习,如果以后要研究SpringBoot中的内置Tomcat(其他 Servlet 容器,一般来说不能说是Tomcat整体,只是其负责处理网络操作、HTTP 请求的路由、请求参数的解析等与网络通信相关的任务)那么建议研究对应的依赖(因为自动配置,所以SpringBoot本身其实并没有什么代码,都是依赖造成的,而依赖是存在启动时完成某些处理,比如在静态代码中,当加载某个类时,静态就会处理,特别的是自动配置时操作反射变成类)