SpringBoot
前言
在此之前学习的有:
javaSE: OOP(面向对象)
MySQL:持久化层
html+CSS+js+jquery+框架:视图层
javaweb:独立开发MVC三层框架的原始网站
ssm整合:框架极大的简化了整体的开发流程,配置也开始较为复杂;
此阶段项目打包都是在war包,整体程序也都是在Tomcat中运行
springBoot就是spring的简化升级版本,核心就是自动装配
springBoot打包都是用的jar包,同时内嵌Tomcat; 微服务架构
在这个时候服务越来越多,就出现了springCloud;
微服务整题构架示意图:
springBoot快速入门
回忆:什么是spring
spring是一个轻量级开源框架,2003年兴起,
是为了解决企业及应用开发的复杂性而创建的,用来简化开发
spring是如何简化java开发的:
spring为了降低java开发的复杂性,采用了以下四种策略
- 基于POJO(实体类)的轻量级和最小侵入性编程
- 通过IOC,依赖注入(DI)和面向接口实现松耦合;
- 基于切面(AOP)和管理进行声明式编程
- 通过切面和模板减少样式代码;
springBoot简介:
springBoot其实不是什么新的框架,它只是默认配置了很多框架的使用方式,就像maven整合了所有的jar包,springBoot整合了所有的框架.
springBoot出身名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,SpringBoot已经当之无愧称为java领域最热门的技术
springBoot主要优点:
- 为所有spring开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和xml配置的要求
微服务
微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合;可通过http(不唯一)的方式进行互通.
说白了微服务就是一个个的不同业务,但是由于分布式开发的原因,需要在不同的电脑上部署同一项目的不同业务,也就是将一个个的业务拆分出来,这就是微服务
比如写好springmvc和Controller之后只需要提供一个接口
单体应用架构
单体应用架构就是指将一个应用的所有服务都封装在一个应用中,就比如之前做的超市订单管理系统
- 好处是易于开发和测试;也十分方便部署,当需要进行扩展时,只需要将war包进行复制,然后放在多个服务器上,再做个负载均衡就可以了.
- 单体应用架构的缺点是,无论要修改什么地方都需要停掉整个服务,然后再重新打包部署这个应用war包,特别是对于一个大型应用,不可能将所有内容都放在一个应用中,不然如何维护如何分工合作都是问题
微服务架构成型
所谓微服务架构,就是把每个功能元素独立出来,再把土里出来的功能元素进行动态组合,需要的功能元素拿去组合,需要多一些时可以整合多个功能元素,
所以微服务无架构是对功能元素进行复制,而没有对整个应用进行复制
这么做的好处是节省调用资源,
并且每一个功能元素的服务都是一个可替换的,可独立升级的软件代码
满足高内聚低耦合的需求
一个大型系统的微服务架构就像一个复杂交织的神经网络,每一个神经元都是一个功能元素,他们各自玩橙子姐功能,然后通过http相互请求调用,比如一个电商项目,差缓存,连接数据库,浏览网页,结账,支付等服务都是一个个独立的功能服务,都被微小化了,他们作为一个个微服务共同构建了一个庞大的系统,如果修改其中一个功能,只需要更新其中一个功能服务单元就行了,
但是这种庞大的系统架构给部署和运维带来了不少压力,但是spring也带来了构建大型分布式微服务的全套全程产品:
- 构建一个个功能独立的微服务应用单元,可以使用springboot来进行快速构建应用
- 大型分布式网络微服务的调用,这部分由springcloud来完成,实现分布式
- 在分布式中间,进行流式数据计算,壁橱里,我们可以用spring could data flow
第一个springboot程序
首先环境:
jdk 1.8
maven 3.6.1
springboot 最新版
idea
官方网站可以直接快捷创建一个项目:
idea中也可以创建,但本质上还是利用官网
idea创建springboot大坑注意:
创建一个初始项目时会,默认给你一个新的maven仓库然后在里面下载插件,由于没有阿里云镜像加速,会在加载pom.xml文件时卡住甚至卡死,
解决办法是找到已经默认下载的maven仓库,给它删了
C:\Users\20458\.m2\wrapper
然后在idea中设置原本的maven地址,包括setting.xml配置文件
即便有阿里云镜像加速,可能依然需要下载一定时间的配置文件,
标准项目结构如下:
controller,dao,pojo,service都是二次创建的
创建包也必须在XXXApplication.java同级目录下,否则无法读取
(约定大于配置)
而这个Application.java为程序的主入口,重申:二次创建包必须在Application.java同目录下
运行Application.java时
出现控制台打印以下代表成功:
之后就可以直接访问设置的8080端口了,甚至直接写一个Controller然后@RequestMapping("/XXX")都能直接访问,注意,到现在我们没有写任何xml配置文件
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//震惊了,这特娘什么都没有配置就能直接访问,当时ssm光一个依赖配置都能拉半天
@RestController
//@RestController用来返回字符串
public class HelloController {
@RequestMapping("/hello")
public String hello(){
//调用业务,接收前端参数
return "hello,World";
}
}
并且通过点击主入口的注释@SpringBootApplication/@SpringBootConfiguration/@Configuration就会发现到最后本质上还是一个spring的@Component组件
springBoot项目pom.xml字段解释:
如下为依赖文件:
<?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:有一个父项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web依赖:集成tomcat,dispatcherServlet,xml-->
<!-- web服务启动器,如果没有导入web依赖,在pom文件中直接插入此标签就可以与web项目一样-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-boot-starter所有的springBoot依赖都是用这段开头的-->
<!-- 单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 打包jar插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
可以发现pom.xml主要有四个组成部分:
- 项目元数据信息:创建时候输入的Project Metadata部分,也就是Maven项目的基本元素,包括:groupld、
artifactld、version、name、 description等 - parent:继承s pring - boot -starter-parent的依赖管理,控制版本与打包等内容
- dependencies:项目具体依赖,这里包含了s
pring-boot-starter-web用于实现HTTP接口(该依赖中包含了Spring MVC),官网对它的描述是:使用Spring
MVC构建Web(包括RESTful)应用程序的入门者,使用Tomcat作为默认嵌入式容器。;
spring-boot-starter-test用于编写单元测试的依赖包。更多功能模块的使用我们将在后面逐步展开。 - build:构建配置部分。默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把SpringBoot应用打包成JAR来直接运行。
PS:关于项目打包只需要在右侧maven菜单栏中点击package就可以自动打包为一个可执行的jar文件,打包后会以缓存文件的形式出现在target栏中
这样就是打包成功了,打开方式老秦用的windows PowerShell,我不知道是什么玩意,明天再弄吧,已经两点了
注意:
新建项目老是使用默认maven仓库地址,可在如下中进行设置
注:修改新建项目信息还是不行,网上搜说是idea2020.2.2版本的bug
可以创建之后卡住的时候结束进程,然后找到地址删除.mvn文件夹,再打开试试
扩展小知识:
快捷改端口号:
springboot改端口号只需要在配置文件中添加下列一行代码后重启即可
springboot修改彩蛋显示(banner,也就是运行时显示的spring立体字):
首先在网上搜索springboot banner在线生成,找到喜欢的内容然后复制,
再在springboot项目中的resource目录下创建一个banner.txt文件,之后就会被自动识别,并更换了:
springboot自动装配原理:
(加薪就靠用这个吹b了)
自动装配:
pom.xml
核心版本依赖:
- spring-boot-dependencies: 核心依赖在父工程中
首先打开pom.xml,连续点击parent标签中的artifactId进入父级目录文件
也就是点击spring-boot-starter-parent,进入后再点击spring-boot-dependencies可以发现有大量的jar包版本
在写或者引入一些springboot依赖的时候,不需要指定版本,就是因为有这些版本仓库
启动器:
可自定义
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
说白了,就是springboot的启动场景;
比如创建了一个spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖
springboot会将所有的功能场景都变成一个个的启动器,
如果我们需要使用什么功能,就只需要找到对应的启动器starter
主程序:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
//@SpringBootApplication :标注这个类是springboot的一个应用
public class DemoApplication {
public static void main(String[] args) {
//将springboot应用启动
SpringApplication.run(DemoApplication.class, args);
}
}
CSDN某博主版讲解:
springboot关于自动装配的源码在spring-boot-autoconfigure-2.4.0.jar中:
首先在主程序上就可以看出自动装配肯定和注解有关系
工作原理剖析:
@EnableAutoConfiguration
@SpringBootApplication是一个复合注解,或者说是派生注解,在@SpringBootApplication中有一个注解1@EnableAutoConfiguration,翻译成人话就是开启自动配置,点开之后其源码定义如下:
而这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelector的selectImports()方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factories的jar包
spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件。
这个spring.factories文件也是一组一组的key=value的形式,只不过其中一个key是EnableAutoConfiguration类的全类名,而它的value是一系列xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔:
这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(…)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中
个人整合
老秦说selectImports()组件选择器选择的是pom.xml中配置信息
然后并且引入了同类另外的一个方法AutoConfigurationEntry(),引入的方法中发现有一个集合:
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
这个集合本身获取了所有的配置,如何获取的呢?
点开getCandidateConfigurations方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
从这里开始终于特娘的连接上了csdn博主说的SpringFactoriesLoader.loadFactoryNames()
同时注意的是此方法导入了两个参数:
getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader()
点开第一个方法参数,可以看到该方法返回了EnableAutoConfiguration注解的类,也就可以获得类中的所有配置说白了就是将用到@EnableAutoConfiguration注解的类当做参数了
然后点开SpringFactoriesLoader的loadFactoryNames()方法
发现结尾返回了
loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
再点开loadSpringFactories()方法,发现利用了一个常量获取了项目资源配置
而这个常量在一开始就已经被声明了:
也就是:
那么接下来就是从这些资源中遍历所有的urls.nextElement();
遍历完成之后,封装为一个Properties最后供我们使用
加载过程概述
- @SpringBootApplication
- @EnableAutoConfiguration自动导入包
- @Import(AutoConfigurationImportSelector.class),点开AutoConfigurationImportSelector
- AutoConfigurationImportSelector类中selectImports()声明了AutoConfigurationEntry()对象
- AutoConfigurationEntry()类本身又声明了一个集合:
List< String> configurations =
getCandidateConfigurations(annotationMetadata, attributes); - 点开集合的组合参数方法getCandidateConfigurations()发现此方法又有一个集合 List< String>configurations =
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader()); - 再点开loadFactoryNames()方法,就会发现所有资源都被加载到配置类中
那么加载了什么资源呢?点开classLoader.getResources(FACTORIES_RESOURCE_LOCATION);中的FACTORIES_RESOURCE_LOCATION,
就会发现类中定义的一句核心代码:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
一切真相大白,每一个maven包下已经定义好的spring.factories资源文件封装的资源被一步一步整合到了一句@SpringBootApplication中,在run方法启动时直接全部自动装配进去了
注意
springboot所有的自动配置都在启动类中被扫描并加载:spring.factories所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入对应的start,就有对应的启动器,有了启动器,我们的自动装配就会生效,然后就配置成功
- springboot在启动的时候,从类路径下/META-INF/spring.factories获取指定的值
- 将这些自动配置的类导入容器,自动配置就会生效,帮助进行自动装配
- 整合javaEE,解决方案和自动配置的东西都在springboot-autoconfigure-2.2.0.RELEASE.jar这个jar包
- 这个包会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器
- 容器中也会存在非常多的XXXautoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件,并自动配置,@Configuration,javaConfig
主启动类运行原理:
主启动类可以看到main方法(注意,此处的main方法代表开启了一个服务)只有一行代码:
SpringApplication.run(DemoApplication.class, args);
该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行
SpringApplication类主要做了下面四件事:
- 推断应用的类型是普通的项目还是web项目
- 查找并加载所有可用初始化器,设置到initializers属性中
/ - 找出所有的应用程序监听器,设置到listeners属性中 - 推断并设置main方法的定义类,找到运行的主类
面试点:
关于springboot谈谈你的理解:
应该注意自动装配和run()方法方面
而run方法回答时注意以下几点:
首先springApplication判断应用的类型是普通项目还是web项目,推断当前设置main方法的定义类,并找到运行的主类
然后run方法中有一些全局存在的监听器,监听器用来获取上下文并处理一些bean,
yaml语法讲解
为什么要使用yaml语法?
首先springboot使用的是一个全局配置文件,配置文件名称是固定的同时在配置文件中也有说明properties文件和yaml文件都是可以使用的,而官方不推荐使用properties文件,所以就有必要了解了.
并且yaml可以直接给实体类赋值
application.properties语法结构为:
- key=value
application.yaml语法结构为:
- key: (空格) value
yaml语法要注意的点不多,如下:
#yaml存普通的键值对:
name: xige
#yaml还可以存properties文件不能存的对象
#yaml语法对空格的要求很严格,这里代表name为student的属性
#但是如果name行开头的空格没了,就变为两个不同的对象了
#同样的,如果name属性下面的age那行再多个空格就代表age是name的属性了
student:
name: xige
age: 22
#行内写法:
students: {name: xige,age: 2}
#数组:
pets:
-cat
-dog
-pig
pet: {cat,dog,pig}
给属性赋值的几种方式
普通声明赋值:
输出在最下面,此处略过不说
yaml赋值
yaml赋值要利用到一个特殊注解:@ConfigurationProperties(prefix = “XXX”)
注意prefix内为application.yaml内的对象名
不管yaml文件类名首字母是否大写,prefix注解首字母一定要小写
此注解作用就是将配置文件中配置的每一个属性的值,映射到这个组件中去;
告诉springboot降本类中所有属性和配置文件中相关的配置进行绑定
但是!!这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能,说人话就是必须和@Component搭配使用
使用properties格式的文件好像也可以使用成功,但是这里不做过多尝试,
往后mybatis或者其他地方需要使用到配置文件的都可以使用yaml来进行复制
如下图:(红色警告不用管也可以运行)
同时,利用yaml文件赋值还可以直接使用一些简易的语法进行进阶操作,比如直接在配置文件中进行加密,节省到吗量之类的:
指定配置文件赋值
首先说明,这个方法很蠢,这里只做演示
首先自定义一个文件名,然后利用注解@PropertySource(value = “classpath:XXX.properties”)进行引入配置文件,
引入之后还要一个一个从配置文件中给属性进行定义
不过需要注意的是使用properties文件进行赋值的时候要注意在idea的Setting中设置一下编码格式,setting–>FileEncoding–>utf-8
属性赋值结论:
配置yml和配置properties都可以获取到值,但是推荐使用yml
如果在某个业务中只需要获取配置文件中的某个值,可以使用@Value
如果说专门编写了一个javaBean来和配置文件进行映射的时候就可以直接使用@ConfigurationProperties,这是最新也是最简便的
JSR303验证
首先讲一下一些拓展术语:
松散绑定:
松散绑定就是比如yaml中定义的属性为last-name,那么这个和lastName是一样的,后面跟着的字母默认是代谢的,这就是松散绑定(感觉没用)
JSR303校验就是用来规范输入内容的,
但是JSR303校验不可以与@Value搭配使用,因为@Value会跳过校验进行赋值
比如说要输入邮箱,就必须按照邮箱的数据格式以.com结尾
如下:
首先导入一个springboot的启动器,不然无法在属性上使用@Email注解
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
<scope>compile</scope>
</dependency>
导入依赖之后记得刷新一下maven,不然无法检测
之后对其进行规范格式之外的赋值时就会报错:
这只是一种最简单的使用方法除了@Email拓展的还有:
还有:
多环境装配以及配置文件位置
配置文件读取位置
首先根据官网说明环境配置文件一共有四种路径可供直接读取:
说人话就分别是
项目根目录新建的config文件夹下的application.yaml
直接在项目根目录下的application.yaml
src/main/java源码文件夹或者resource文件夹下新建config文件夹下的application.yaml
再有就是resource文件夹下直接新建application.yaml了
优先级就是从上往下数的,相同目录优先使用config文件夹下的配置文件
多环境装配:
首先实际开发的时候一般来说需要配置多套环境的,比如开发一个环境,测试一个环境,运行一个环境等等,而springboot已经帮我们想好了
可以通过application-XXX.properties的形式来建立配置文件:
如图所示,上述文件都会被自动转化为配置文件,不过默认使用application.properties,使用其他环境的时候需要在application.properties中专门进行声明:
#springboot的多环境配置,可以选择激活哪一个配置环境
spring.profiles.active=dev
后面的dev就是-xxx的后缀,如果使用测试环境就是:
并且如果使用yaml格式的配置文件的话,可以实现多文档模块,用人话来说就是单个文件,可以读取不同配置环境
server:
port: 8088
spring:
profiles:
active: dev
---
server:
port: 8081
spring:
profiles: dev
---
server:
port: 8082
spring:
profiles: test
自动配置原理再理解
看过了自动装配的原理流程,又学过了application配置文件的作用,两者之间其实也是有联系的,
首先所有可以在application配置文件中可以生米那个修改属配置类中已经声明过了,
如下为一个在spring.factories文件中的一个HttpEncodingAutoConfiguration(翻译为http编码自动组态)配置,
这个配置可以看到开头的注解就导入了一个properties类
再次点开这个类就会发现这个类又导入了一个server开头的文件,并且这个类自身定义的属性就是application.yaml可以配置的属性,或者说application.yaml能配置的属性必须是在配置类中已经声明的
甚至直接点application.yaml配置文件中定义的属性都能直接跳转到对应的类:
总结:
在我们配置文件中能配置的属性,都存在一个固定的规律一定会有spring.factories文件声明的XXXautoConfiguration组件进行自动装配,
而自动装配又有自带的默认值,而这些默认值都是通过一个XXXproperties的文件进行修改,
而这个XXXproperties又和application.yaml进行绑定,所以就可以使用自定义的配置了
老秦の精髓:
- springboot启动会加载大量的自动配置类
- 我们看需要的功能有没有在springboot默认写好的自动配置类中存在
- 再看这根自动配置类中到底配置了哪些组件(只要问要用的组件存在其中,我们就不需要在进行手动配置了)
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性的值就行了
XXXAutoConfiguration: 自动配置类,给容器中添加组件
XXXProperties: 封装配置文件中相关的属性
拓展一下:
ConditionalOn是spring的底层注解: 根据不同的条件来判断当前配置或者类是否生效
另外当在application.yaml配置debug: true时就可以在运行时查看有哪些自动配置类是效,哪些没有生效:
包括以后遇到二手项目的未知配置信息也可以点进去查看配置信息,然后在spring.factories中CTRL+F搜索一下就可以找到具体的配置组件了
WEB开发探究:
springboot的自动装配,说到底都是MATA-INF下面的spring.factiories文件中XXXXAutoConfiguration向容器中自动配置组件
XXXXProperties自动配置类,装配配置文件中自定义的一些内容,而这些组件的配置信息也可以通过绑定的application.yaml进行修改,但是能修改的都是组件中已经声明过的信息
那么springboot的web项目要解决的问题就有:
- 导入静态资源
- 首页
- springboot没有能写jsp的地方,所以就需要使用模板引擎(Thymeleaf)
- 装配扩展springmvc
- 增删改查
- 拦截器
- 国际化(中英文切换)
静态资源导入探究
一般新建的springboot项目中没有静态资源目录,那么静态资源怎么导入呢?
可以通过源码的三个if得出结论:
获取静态资源源码
首先双击shift搜索WebMvcAutoConfiguration可以发现有个addResourceHandlers方法:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//首先判断静态资源是否已经被自定义,如果已经被自定义就直接返回并声明默认资源处理失效
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)
.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)
.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
}
}
第一个if:
首先判断是否在application中声明过静态资源路径,如果已经有声明,则往后的几个静态资源路径直接无效化
第二个if:
判断中看到资源可以在一个叫webjars的目录中导入的
什么是webjars?
webjars其实就是另一种maven,都是用来导入资源的
百度webjars官网往下拉就可以发现像maven一样的资源导入标签,使用方法也与maven一样,导入到pom.xml中就行了
(图片中网络加载太慢导致maven的信息没有及时刷新出来)
导入之后刷新一下就可以在lib目录下找到相关的文件了:
运行时http://localhost:8080/webjars/jquery/3.4.1/jquery.js也可以搜素到相关的代码,但是一般来说不会使用这种方法
第三个if:
判断的方法就可以看出可以直接识别静态资源的路径,点开getStaticPathPattern(),会发现该方法用this指定了一个private String staticPathPattern = “/**”;也就是说只要是在当前目录的都可以识别
点开getStaticLocations()会发现这个方法是用this绑定的staticLocations,而staticLocations的资源监测路径已经被定义好了:
public static class Resources {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
分别是:
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
经测试,在以上四个目录下的页面可以直接以http://localhost:8080/hello.js的方式获取,并且相同的页面四个路径的优先级为:
resources>static>public
首页和图标定制
首页设置:
首先依然是WebMvcAutoConfiguration类,然后搜索index就可以发现如下方法:
单纯从index.html就可以看出定义为index.html的就会被判定为首页,
所以,按照通常来讲,一般把首页定义在resources目录下的public,static,sources文件夹都可以
但是正常来说,一般我们都会通过Controller来跳到首页中,
按照这么来说的话就需要将首页放到template目录中,在templates目录下的所有页面,只能通过Controller来进行跳转,相当于原来的WEB-INF目录,
并且要想在Conroller中跳转到index页面,还需要thymeleaf模板引擎依赖.
图标定制:
图标定制其实就是是修改网页上的title标签左侧的小图标
图标定制功能可以在springboot的低版本中找到相关的方法,高版本没有找到,但是依然可以使用,
老版本需要首先在application.yaml中设置默认图标失效:
#设置标签图标首先要关闭默认的图标
spring:
mvc:
favicon:
enabled: false
然后在resource目录下粘贴一个图片,并改名为:favicon.ico
新版本只需要粘贴并改名就可以了:
效果如下:
thymeleaf模板引擎:
springboot中不建议使用jsp,而是使用更加成熟的模板引擎,这里老秦推荐使用thymeleaf模板引擎,地址:
1、Thymeleaf官网:
https://www.thymeleaf.org/
2、Thymeleaf在Github 的主页: https://github.com/thymeleaf/thymeleaf
3、Spring官方文档https://docs.spring.io/springboot/docs/2.1.6.RELEASE/reference/htmlsingle/#using-boot-starter
PS:这里我springboot2.4.1版本pom依赖导入总是变红,搞得我又重新建立了一个模板,直接添加了thymeleaf依赖,
如果需要使用thymeleaf,只需要导入对应的starte就行了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
需要注意的是往后项目的静态网页都必须放在template目录下
thymeleaf的简单测试:
建立一个简单的controller,写一个html,访问没问题
但是controller中model.addAttribute(“msg”,“hello,thymeleaf”);传递一个值后是取不出来的
需要使用thymeleaf的专用语法,th:XXX,与vue的v-bind含义一样
并且html文件头需要导入一下
< html lang=“en” xmlns:th=“http://www.thymeleaf.org”>
<h1>hello</h1>
<!--所有的html元素都可以被thymeleaf替换接管,语法格式为th: 元素名-->
<div th:text="${msg}"></div>
thymeleaf语法
thymeleaf常用th属性解读
html有的属性,Thymeleaf基本都有,而常用的属性大概有七八个。其中th属性执行的优先级从1~8,数字越低优先级越高。
- th:text :
设置当前元素的文本内容,相同功能的还有th:utext,两者的区别在于前者不会转义html标签,后者会。优先级不高:order=7
- th:value:
设置当前元素的value值,类似修改指定属性的还有th:src,th:href。优先级不高:order=6
- th:each:
遍历循环元素,和th:text或th:value一起使用。注意该属性修饰的标签位置,详细往后看。优先级很高:order=2
- th:if:
条件判断,类似的还有th:unless,th:switch,th:case。优先级较高:order=3
- th:insert:
代码块引入,类似的还有th:replace,th:include,三者的区别较大,若使用不恰当会破坏html结构,常用于公共代码块提取的场景。优先级最高:order=1
- th:fragment:
定义代码块,方便被th:insert引用。优先级最低:order=8
- th:object:
声明变量,一般和*{}一起配合使用,达到偷懒的效果。优先级一般:order=4
- th:attr:
修改任意属性,实际开发中用的较少,因为有丰富的其他th属性帮忙,类似的还有th:attrappend,th:attrprepend。优先级一般:order=5
标准表达式语法
- 简单表达式 simple expressions
${…} 变量表达式 Variable Expressions
*{…} 选择变量表达式 Selection Variable Expressions
#{…} 消息表达式 Message Expressions
@{…} 链接url表达式 Link URL Expressions
~{…} 代码块表达式 Fragment Expressions
常用th属性使用
Thymeleaf属性的使用注意点:
一、若要使用Thymeleaf语法,首先要声明名称空间: xmlns:th=“http://www.thymeleaf.org”
二、设置文本内容 th:text,设置input的值 th:value,循环输出 th:each,条件判断 th:if,插入代码块 th:insert,定义代码块 th:fragment,声明变量 th:object
三、th:each 的用法需要格外注意,打个比方:如果你要循环一个div中的p标签,则th:each属性必须放在p标签上。若你将th:each属性放在div上,则循环的是将整个div。
四、变量表达式中提供了很多的内置方法,该内置方法是用#开头,不要与#{}消息表达式弄混。
五、th:insert,th:replace,th:include 三种插入代码块的效果相似,但区别很大。
拓展注意点
字面量
‘one text’,‘another text’…文本
0,1,3.0,12.45… 数值
true false 布尔类型
null 空
one,sometext,main 文本字符
文本操作
- 字符串连接
|The name is ${name}| 字符串连接
算数运算
,+ - * / % 二元运算符
- 负号 一元运算符
布尔操作
and or 二元操作符
! not 非 一元操作符
关系操作符
< > >= <= gt lt ge le
== != eq ne
条件判断,不要在前端页面使用if…else.推荐使用三元运算符
(if) ? (then) if-then
(if) ? (then):(else) if-then-else
测试:
//在template目录下的所有页面只能通过Controller来进行跳转
//这个需要模板引擎的支持,thymeleaf
@Controller
public class InedxController {
@RequestMapping("/test")
public String test(Model model){
model.addAttribute("msg","<h1>hello,thymeleaf</h1>");
//Arrays.asList(),直接将一系列字符串转为集合
model.addAttribute("users", Arrays.asList("xige","jianshen","xiaoguoguo"));
return "test";
}
}
前端页面:
<h1>hello</h1>
<div th:text="${msg}"></div>
<!--utext代表不被转义-->
<div th:utext="${msg}"></div>
<hr>
<!--这里意思为先将users对象foreach,然后再将每个对象都转为user进行展示输出-->
<h3 th:each="user:${users}" th:text="${user}"></h3>
<!--使用下面这句效果一样,但是更建议使用上面这种-->
<h3 th:each="user:${users}">[[ ${user} ]]</h3>
效果展示:
MVC自动装配(重点)
首先查看官方文档:
https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-developing-web-applications.html
从官方文档中可以看出:
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc
翻译过来就是:
如果您想保留Spring Boot MVC功能并且想要添加其他MVC配置(拦截器,格式化程序,视图控制器和其他功能),则可以添加自己的类型为WebMvcConfigurer的@Configuration类,但不添加@EnableWebMvc
PS:一旦标注@EnableWebMvc,mvc就会全面接管,你的自定义配置也就不会生效了
也就是说需要使用@Configuration注解,并且类型必须为WebMvcConfigurer
搜索WebMvcConfigurer发现这是一个接口,那么实现它就好了
PS:其中原理老秦在源码打断点讲的我有点蒙,听不懂,老秦也说不怎么用,暂时挖个坑
代码如下:
//全面扩展 springmvc
//如果想定制一些功能,只需要写这个组件,然后把它交给springboot,springboot就会帮我们自动装配
@Configuration //将这个类变为一个配置类
public class MyMvcConfig implements WebMvcConfigurer {
//实现了视图解析器接口的类,我们就可以把他看做视图解析器
//@Bean就可以放在Bean里面
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义一个视图解析器 MyViewResolver
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
}
扩展springMVC
##自定义的配置日期格式化
spring:
mvc:
format:
date-time: yyyy-MM-dd HH:mm:ss
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//设置指定视图名(/xige)控制其跳转到另外一个特定视图(test)
registry.addViewController("/xige").setViewName("test");
}
在springboot中有很多的XXX Configuration,会帮助我们进行扩展配置,只要看到了这个东西,我们就要注意了,因为它改变或者扩展了springboot原有的一些东西,
员工管理系统
要建立系统,这里首先要使用bootstrap模板,官网地址:
https://v3.bootcss.com/getting-started/
这里没弄好,不会用bootstrap的源码模板,直接找到了群里面的web素材
直接新建两个pojo实体类,员工表和部门表然后私有化属性
部门表只有id和name
员工表五个基本数据和一个日期,全部使用lombok,日期直接在有参构造中new date了
javaweb注解:
Service 业务类专用
Repository dao实现类专用
Controller web层专用
Component 通用
然后是两个表的dao实现类创建方法(当做执行sql),(一般整合mybatis才会叫mapper,我这边直接用了)
//部门dao
@Repository
public class DepartmentMapper {
//模拟数据库中的数据
private static Map<Integer, Department> departments = null;
static{
departments=new HashMap<Integer,Department>(); //创建一个部门表
departments.put(101,new Department(101,"生产部"));
departments.put(102,new Department(102,"技术部"));
departments.put(103,new Department(103,"销售部"));
departments.put(104,new Department(104,"财务部"));
departments.put(105,new Department(105,"市场部"));
}
//获得所有部门信息
public Collection<Department> getDeparment(){
return departments.values();
}
//通过id得到部门
public Department getDeartmentById(Integer id){
return departments.get(id);
}
}
员工dao:
//员工dao
@Repository //spring托管dao层实现类专用
public class EmployeeMapper {
//模拟数据库中的数据
private static Map<Integer, Employee> employees = null;
//员工有所属的部门
@Autowired
private DepartmentMapper departmentMapper;
static{
employees=new HashMap<Integer,Employee>(); //创建一个部门表
employees.put(1001,new Employee(1001,"AA","Q2045807586@qq.com",1,new Department(101,"生产部")));
employees.put(1002,new Employee(1002,"BB","W2045807586@qq.com",0,new Department(102,"技术部")));
employees.put(1003,new Employee(1003,"CC","E2045807586@qq.com",0,new Department(103,"销售部")));
employees.put(1004,new Employee(1004,"DD","R2045807586@qq.com",1,new Department(104,"财务部")));
employees.put(1005,new Employee(1005,"EE","A2045807586@qq.com",1,new Department(105,"市场部")));
}
//主键自增
private static Integer initid=1006;
// 增加一个员工
public void save(Employee employee){
if (employee.getId()==null){
employee.setId(initid++);
}
//这里要先看括号里面的,总体是利用传过来的员工对象得到部门,再得到部门的id,然后再利用部门的id得到部门,然后再用这个部门给传递过来的员工对象对的部门赋值,
//哈哈哈哈哈.原地TP,不知道老秦这代码具体的含义是什么
employee.setDepartment(departmentMapper.getDeartmentById(employee.getDepartment().getId()));
employees.put(employee.getId(),employee);
}
//查询全部员工信息
public Collection<Employee> getAll(){
return employees.values();
}
//通过id查询员工
public Employee getEmployeeById(Integer id){
return employees.get(id);
}
//根据id删除员工,id为主键,主键一没有,信息也就没了
public void delete(Integer id){
employees.remove(id);
}
}
首页实现:
所有页面的静态资源都需要使用shymeleaf接管
首先看一下config包下的配置类,config配置类可以有多个,不同配置类对应不同的配置信息
配置类也可以配置首页,这样就不用在controller中再配置了:
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
}
我这里直接在官网模板F12后CV的,有些样式没有按照标准来,这里统一改一下:
首先html标签头需要修改为以下内容:
注意不要搞成w3school了
<html lang="en" xmlns:th="http://www.thymeleaf.org">
然后还需要按照thymeleaf的模板规范来写,比如:
超链接标签的href需要改为th:href,并且标签内的超链接需要包含在@{}中,
需要注意的是,括号中是以static包下开始的,并且不知为何,静态资源包在static包下的时候不能被其他包包括,不然就报错
比如bootstrap的css核心文件链接:
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap-theme.min.css}" rel="stylesheet">
并且使用@{}后,如果在application配置文件中修改servlet的映射目录,页面的样式也会自动匹配,依然有效
#关闭模板引擎的缓存
spring:
thymeleaf:
cache: false
#更改映射目录
server:
servlet:
context-path: /xige
页面的国际化
国际化其实就是修改页面显示哪国语言
页面国际化的步骤有:
配置i18n文件
如果需要在项目中进行按钮自动切换,我们需要自定义一个组件LocaleResolver
记得将自己写的组件配置到spring容器中,@Bean
首页传参使用#{}
首先需要设置编码格式:
保证设置正确之后,在resources目录下新建一个叫i18n的包,(PS:i18n其实就是国际化internationalization的简读,18代表i和n之间有18个字母,与k8s一样),然后在包中创建login.properties和login_zh_CN.properties文件,就会发现两个文件夹被自动合并成为一个了,并且右击还可以再直接创建资源包:
按图添加词条属性之后,文本中也都会全部添加进内容
然后在application配置文件中写好声明:
再然后在页面中需要使用国际化的标签中写好thymeleaf的国际化语法
th:text="#{login.tip}
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
利用此方法将所有的固定词改为国际化动态语句,到最后就可以进行显示了
但是还需要再修改一下,要点下面的中英文切换,进行自动转化才行
那么就需要点击后有不同的请求才行:
然后就是写一个方法接收请求并注册Bean到spring容器中了
//国际化解析器必须实现LocaleResolver接口
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数
String language = request.getParameter("l");
Locale locale = Locale.getDefault();//如果没有就使用默认语言
//如果请求的连接携带了国际化参数
if (!StringUtils.isEmpty(language)){
//将传递过来的参数分割成国家和地区
String[] split = language.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
接下来就是在MyMvcConfig类中注册bean:
//自定义的国际化组件就生效了
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
这样一来就完成了,虽然我不知道注册bean之后,写的i18n.login文件会以怎样的形式被执行,当然对于这种轮子我们只需要会用就可以了,吃饭没必要从种地开始
登录功能实现
这里登录因为没有数据库所以简单化,
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model){
//具体的业务,登录成功重定向到主体页面
if(!StringUtils.isEmpty(username) && "666666".equals(password)){
return "redirect:/main";
}
//登录失败
model.addAttribute("msg","密码是666666");
return "index";
}
}
首页显示model模型返回的消息
<!-- 如果msg的消息为空,则不显示,这里的三元运算符其实没什么用-->
<p th:text="${msg}" style="color: red" th:if="${not #strings.isEmpty(msg)}"></p>
登录拦截器
跟之前ssm的超市管理系统一样,利用的HandlerInterceptor接口进行拦截,
首先新建一个拦截器类,然后实现拦截器接口,利用请求登录成功之后会有用户session这一条件进行拦截,当然,之前的登录成功请求还没有携带session参数,这些都是需要再额外手动添加上的:(这里还可以利用这条用户名信息,显示在用户登录后的页面上进行个性化显示)
设置这里的拦截器条件也是十分简陋直接,有了用户session就过,没有就不过
注意:返回true就是放行,反之则拦截
//登录拦截器
//只要实现了接口的方法就是一个拦截器,当然也需要实现对应的方法
//此方法返回true就是放行,否则就拦截
public class LoginHandInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//登录成功之后应该有用户的session,就按照这个来进行指定拦截
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser==null){
request.setAttribute("msg","非法登录");
request.getRequestDispatcher("/index").forward(request,response);
return false;
}
return true;
}
}
然后就是在MvcConfig包下新建拦截器,设置拦截器拦截范围,然后再额外设置一下自动跳过拦截器的页面,如下就是拦截器类的的代码:
//添加拦截器配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册拦截器
registry.addInterceptor(new LoginHandInterceptor()).
//设定拦截范围为全部
addPathPatterns("/**").
//设定自动跳过拦截的页面,这里添加了首页的两个跳转链接,登录本身的链接以及静态资源的提取
excludePathPatterns("/index","/","/user/login","/css/*","/js/**","/img/**");
}
展示员工列表
写一个员工列表的Controller:
组合Mapper层,并利用model传递mapper层方法的结果并返回页面展示
@Controller
public class EmployeeController {
@Autowired
EmployeeMapper employeeMapper;
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> emps = employeeMapper.getAll();
model.addAttribute("emps",emps);
return "emp/list";
}
}
当然,前端页面也需要进行接收:
分段复用:(侧边栏及顶部栏)
只需要在页面侧边栏标签中插入下列代码(sidebar为分段 自定义的名称)
th:fragment="sidebar"
然后在其他要用到侧边栏的地方插入一句:
(dashboard为带有fragment的侧边栏,sidebar为引用分段的名称)
<div th:insert="~{dashboard::sidebar}"></div>
或者使用replace(替换)
<div th:replace="~{dashboard::sidebar}"></div>
如果使用include,则会丢失样式
<div th:include="~{dashboard::sidebar}"></div>
就可以实现代码的复用,就像vue一样,相当于变为了一个组件,然后其他用的地方再插入就可以了
这里最后甚至专门写了一个commons.html页面来存放所有页面的公共部分
侧边栏选项高亮:
其实就是选中侧边栏后显示内容时,侧边栏的选项高亮
效果样式为:
原理也很简单,就是在侧边栏复用的情况下,
例如员工管理页面,员工管理页面复用侧边栏时设置使其传递一个参数
方法就是括号,然后设置传递的值(active=‘list’)
active只是自定义的属性名称
<div th:replace="~{commons/commons::sidebar(active='list')}"></div>
然后commons的侧边栏公共组件搞一个三元运算符,
如果有这个参数就执行带有高亮的样式,如果是别的页面选用时自然不会有指定参数传递过来,也就不会显示高亮了
PS:三元运算符就是th:class标签中的
${active==‘list’?‘nav-link active’:‘nav-link’}
<a th:class="${active=='list'?'nav-link active':'nav-link'}" th:href="@{/emps}">
表单内容展示
那么最后终于到了将表单信息编写到页面上的时候了,
这里直接将list页面中< table>标签的< thead>表头内容修改为员工表的姓名,id之类的
同时将< tbody>标签,也就是具体的内容,设置为自动获取特定员工的id,姓名之类的,
如下:
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2>Section title</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>eamil</th>
<th>sex</th>
<th>department</th>
<th>birth</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}"></td>
<td th:text="${emp.getLastName()}"></td>
<td th:text="${emp.getEamil()}"></td>
<!--由于男女设置的0和1代替,所以这里就直接用了三元运算符来简单判断一下-->
<td th:text="${emp.getSex()==0?'女':'男'}"></td>
<td th:text="${emp.getDepartment().getName()}"></td>
<!--这里利用了一个工具类改了日期的显示样式-->
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
<td>
<button class="chartjs-render-monitor">编辑</button>
<button class="chartjs-render-monitor">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</main>
效果如下,按钮具体的效果也可以选用bootstrap的,
三条属性,一个代表按钮,一个代表大小,一个代表样式
比如老秦就用的
< button class=“btn btn-sm btn-primary”>编辑< /button>
< button class=“btn btn-sm btn-danger”>删除< /button>
增加员工:
这里直接新建了一个添加页面,其实就是把list.html表单页面重新复制了一份改名为add.html,原有的侧边栏以及顶部不变,表单本身样式变一下,老秦是从bootstrap里面搞得,我也直接在评论区底部找到了:
这里只复制了add.html中main标签里面的内容,其他的都一样就不写了,注意要把复制过来的添加员工按钮给删了
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>LastName</label>
<input type="text" name="lastName" class="form-control" placeholder="请输入姓名">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="eamil" class="form-control" placeholder="2045807586@qq.com">
</div>
<div class="form-group">
<label>Sex</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department.id">
<!-- 遍历部门表,并将遍历的每一个的名字都显示出来,注意,显示的是部门名字,但是提交的是部门的id-->
<option th:each="dept:${deparment}" th:text="${dept.getName()}" th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" type="text" class="form-control" placeholder="生日是什么时候呢">
</div>
<button type="submit" class="btn btn-primary">确认添加</button>
</form>
这里需要注意的点有:
所有的选项都必须有name而且与pojo的属性一一对应,
<select class="form-control" name="department.id">
<!-- 遍历部门表,并将遍历的每一个的名字都显示出来,注意,显示的是部门名字,但是提交的是部门的id-->
<option th:each="dept:${deparment}" th:text="${dept.getName()}" th:value="${dept.getId()}"></option>
</select>
这里是将部门表全部通过名字的方式展示出来,然后提交的时候使按照value来进行提交的,
并且这里的name=“department.id是因为th:value=”${dept.getId()中value是以id来获得部门的,这部分老秦讲的很模糊,我也不是太明白,不过报错的确是说无法进行类型转换的原因
然后就是controller层的跳转控制了:
@Controller
public class EmployeeController {
@Autowired
EmployeeMapper employeeMapper;
@Autowired
DepartmentMapper departmentMapper;
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> emps = employeeMapper.getAll();
model.addAttribute("emps",emps);
return "emp/list";
}
//添加员工的页面,这里用了Restfor风格,请求一样,但提交的方式不一样,一个是get,一个是post提交
@GetMapping("/emp")
public String toAddpage(Model model){
//查出所有部门的信息
Collection<Department> deparment = departmentMapper.getDeparment();
model.addAttribute("deparment",deparment);
return "emp/add";
}
//将填写的员工添加到list表单页面(数据库)中
@PostMapping("/emp")
public String addEmp(Employee employee){
System.out.println("员工信息"+employee);
//添加的操作
employeeMapper.save(employee);//b调用底层业务方法,保存员工信息
//添加成功之后返回到员工展示界面 还有转发 forword
return "redirect:/emps";
}
}
整体逻辑就是点击添加员工之后超链接get的方式提交到emp控制层,
然后控制层控制其跳转到add页面
add页面添加完内容之后post的方式提交到emp控制层,
然后控制层将内容利用底层代码的save方法保存到list表单,
再跳转到list页面进行保存内容之后的展示
效果如下:
这里需要注意的是这里的生日格式默认必须为2000/12/16的格式,而不能以2000-12-16的格式,否则就会报错,除非在application.yaml中进行如下设置:
spring:
mvc:
date-format: yyyy-MM-dd
修改员工信息:
首先要想修改员工信息,就要首先得到要修改的员工的初始数据,
并且还要有修改时的页面:
这里首先继续复制add页面并改名为update:
并且还需要将原本需要输入的地方先给展示出原本的数据:都是取值,然后展示
<form th:action="@{/updateEmp}" method="post">
<input type="hidden" name="id" th:value="${emp.getId()}">
<div class="form-group">
<label>LastName</label>
<input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="请输入姓名">
</div>
<div class="form-group">
<label>Email</label>
<input th:value="${emp.getEamil()}" type="email" name="eamil" class="form-control" placeholder="2045807586@qq.com">
</div>
<div class="form-group">
<label>Sex</label><br>
<div class="form-check form-check-inline">
<input th:checked="${emp.getSex()==1}" class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input th:checked="${emp.getSex()==0}" class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department.id">
<!-- 遍历部门表,并将遍历的每一个的名字都显示出来,注意,显示的是部门名字,但是提交的是部门的id-->
<option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${deparment}"
th:text="${dept.getName()}"
th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}" type="text" class="form-control" placeholder="生日是什么时候呢">
</div>
<button type="submit" class="btn btn-primary">确认修改</button>
</form>
这里一定要仔细看看,不同的条目是怎么进行输出展示的
然后还需要注意的是,日期一定要跟application上的格式对应
到最后与add页面一样,到最后使用save方法,保存:
//去员工的修改页面
@GetMapping("/emp/{id}")
public String toUpdateEmp(@PathVariable("id")Integer id,Model model){
//查出需要修改的员工数据
Employee employee = employeeMapper.getEmployeeById(id);
model.addAttribute("emp",employee);
//同样需要查到部门信息
Collection<Department> deparment = departmentMapper.getDeparment();
model.addAttribute("deparment",deparment);
return "emp/update";
}
//修改完之后重定向到展示页面
@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
employeeMapper.save(employee);
return "redirect:/emps";
}
PS:这里有一个很神奇的地方,创建和修改的时候性别只能为男,就算创建时选中为女,也会改为男,绝了
删除及404处理:
删除数据贼简单,点击删除携带id调转到controller,然后controller获取到id执行删除方法再跳转到展示页面就可以了:
//删除员工
@GetMapping("/deleteEmp/{id}")
public String deleteEmp(@PathVariable("id")Integer id){
employeeMapper.delete(id);
return "redirect:/emps";
}
404
springboot的404以及500等报错页面也十分简单,只需要在template包下面新建一个error包,然后将报错页面放里面就可以了
整体文件结构如下:
退出登录:
这个不能直接点超链接到首页,因为这样的话退回页面就可以重新回来,
所以也要进controller然后注销session再重定向到首页:
@RequestMapping("/user/logout")
public String loginOut(HttpSession session){
session.invalidate();
return "redirect:/index";
}
到此,完结(才怪)撒花!!!
一共四万多字,纯手打,三天打鱼两天晒网共计花了一个月左右吧,纯小白自学,平常还得复习补充,真特奶奶的令人绝望,不过还是加油吧,静态资源在老秦二群,以后想重新写也是完全可以的,
整合数据库之类的写到下一个博客里面吧,
2021年1月6日 23:18:46 xi哥努力的样子今天还是那么的帅