扎实的基础是为了更容易看懂底层源码,熟练框架使用可以让开发的使用更加简约,
什么是Spring
Spring是如何简化Java开发的:
为了降低Java开发的复杂性,Spring采用了以下4种关键策略:
什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。
随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;
Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。
简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。
Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。
Spring Boot的主要优点:
为什么要约定大于配置:
什么是微服务:
单体架构:
单体架构优缺点:
微服务架构:
微服务架构优点:
如何构建微服务:
微服务这种庞大的系统架构给运维和部署带来了很大的难度,因此,spring为我们带来了构建大型分布式微服务的全套、全流程产品
jdk 1.8 ,maven 3.6.1,springboot最新版,IDEA
springboot项目的搭建可以手动,也可以自动,通常我们选择联网的方式快速在线搭建springboot项目
项目参数根据自己情况来定,选择war包,添加一个 spring web 依赖,springboot会自动生成相关配置
generate生成包,下载到本地,解压缩,用idea加载项目即可,
进入项目,此时项目结构还没有被idea识别,右键pom.xml,将其作为maven项目导入
idea会自动识别项目结构,并下载相关依赖
IDEA联网搭建:
删除这个项目,直接在IDEA中联网搭建,创建项目,选择
下一步,根据自己喜好填写项目信息
添加依赖,我们可以先添加一个spring web
确定创建项目,首次创建springboot需要下载很多依赖,耐心等待
项目联系阶段,我们可以先将不用的文件删除,保持项目干净整洁
通常我们直接使用IDEA联网创建
启动application main方法,即启动项目
从控制台我们可以看到启动信息,springboot内嵌了tomcat,端口号8080,访问测试一下
空项目,所以返回了一个error page
我们以后所创建的所有程序及包结构都要与application同级目录或同级目录的子目录下,否则项目无法正常运行,因为springboot启动时要扫描目录,这个目录就是appllication所在的目录,比如,
添加一个HelloController,返回一个字符串,重启项目,访问测试
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "hello springboot";
}
}
用springboot构建项目,我们甚至都没有配置web.xml,applicationContext.xml,就成功启动了项目,并访问测试,因为springboot在底层实现了自动装配
主启动类:
appication主启动程序有一个注解SpringBootApplication
,进入查看源码
注解中包含了@Component,说明application主启动类本身就是spring的一个组件,
pom.xml初始默认的配置文件:
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.4.4version>
<relativePath/>
parent>
<groupId>com.swygroupId>
<artifactId>springboot-01-helloworldartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>warpackaging>
<name>springboot-01-helloworldname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
spring-boot-starter-parent
,是个远程在线的项目,用来控制版本与打包内容通过观察,我们发现我么在创建项目时,自动添加的依赖都以 spring-boot-starter 开头,我们通过springboot添加的依赖都是以spring-boot-starter开头的,且没有版本号,因为springboot同一帮我们选择好了
当然我们也可以自己在maven仓库上搜索所需的依赖坐标,添加进来,一般我们都使用springboot提供的依赖,如果无法下载或失效,我们就自己添加带版本号的完整依赖
如何打包:双击install即可打包
打包成功
target目录下生成 jar/war 包,一个jar包就是一个独立的程序,我们可以单独将 jar 包提取出来,通过命令行java -jar
运行,它已经可以不依赖IDEA,独立的在JDK环境中运行
如果打包失败,可能是代码出现异常,也可能是依赖失效、冲突,亦或是旧的jar/war残留影响,需要具体问题具体分析调整,只有打包成功,项目才可以说可以启动
这种独立的模块化结构,也为下一步微服务做好了准备
pom.xml配置中,依赖 spring-boot-starter
叫做启动器,如果我们创建项目没有spring web时,可能会有这个单独的依赖,如果添加了spring web依赖,spring-boot-starter
就会添加在web依赖中
springboot的启动配置几乎都在properties或yaml中进行,springboot默认使用了properties,空配置
修改端口号:properties中添加server.port=8080
,默认就是端口8080,许多配置基本上都有提示,减少错误率,加快配置速度
上网搜索springboot banner,搜索喜欢的字符排版,拷贝,
在resources目录下新建banner.txt,粘贴上述字符排版,重启即可
pom.xml 中,parent标签spring-boot-starter-parent
可以点进入查看父级,里面的parent标签 spring-boot-dependencies
可以继续点进去查看父级,
最终我们看到,在spring-boot-dependencies
这个依赖中,我们看到,这个pom.xml中包含了绝大部分的依赖坐标,其中,properties标签控制着各个依赖的版本,这些版本号将随着官方的更新动态更新
dependencyManagement
标签包括了各种依赖坐标,并引入properties属性中的版本号,拼成完整依赖坐标
这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;
回到项目 pom.xml 中,
自动配置:
pom.xml:
启动器:没有启动器,项目将无法启动
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
有了spring-boot-starter
启动器,我们在其名字后面加上各种场景、依赖名字,就会帮我们自动导入该环境的所有依赖,比如
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
启动器:
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
@SpringBootApplication标注了该类是springboot的一个应用,run方法是个静态方法,通过反射加载当前类对象,进行启动
下面,我们深入分析@SpringBootApplication
注解,其中@Target @Retention @Documented @Inherited
四个元注解不在解释,分析每个层级的重点注解
@SpringBootApplication
:最外层
@SpringBootConfiguration
@Configuration:说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件
@Component:这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用
@EnableAutoConfiguration:见名知意, 告诉SpringBoot开启自动配置功能,这样自动配置才能生效,以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置
@AutoConfigurationPackage:表示自动配置包
@Import({Registrar.class}):Spring底层注解, 给容器中导入一个组件导入选择器,Registrar中包含了元数据meta,包名packagename
@Import({AutoConfigurationImportSelector.class}):给容器导入组件自动配置,包注册,AutoConfigurationImportSelector中有环境,资源加载器,选择组件,有方法getCandidateConfigurations可以获取所有配置,进入方法内,可以看到获取候选的配置
@ComponentScan(...):用于包扫描,它对应XML配置中的元素,自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
里面的注解各种操作,其实就是为了让@SpringBootApplication标注的启动类可以将所有资源导入
其中 getCandidateConfigurations方法获取候选配置,方法里涉及到了 META-INF/spring.factories ,这是自动配置的核心文件,在依赖包中可以找到
打开可以发现,里面有各个情况下的配置对应的全限定类名
我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration,
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
每个类中,都有对应的Java代码进行配置
结论:
最初以为就是运行了一个main方法,没想到却开启了一个服务
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
SpringApplication.run分析
分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;
这个类主要做了以下四件事情:
查看 SpringApplication 构造器
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
关于SpringBoot 的理解:
SpringBoot使用一个全局的配置文件在resources中, 配置文件名称是固定的,application.properties 或 application.yaml,
官方推荐使用 application.yaml,因此,通常我们创建新项目后,都会删除默认的application.properties,重新创建 application.yaml
编写 springboot 的配置文件时,我们可以看到有大量的提示可以帮助我们快速生成
application.properties
application.yml
配置文件的作用 :
比如,我们配置tomcat启动默认端口号,
application.properties
server.port=8090
application.yml,
server:
port: 8090
注意,yaml的配置结构有缩进,有空格,格式不对就会失效
我们可以在配置yaml时,可以写成server.port
的方式,利用idea自动功能,生成标准结构
YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
配置文件后缀可以写 yaml 也可以写成 yml
这种语言以数据作为中心,而不是以标记语言为重点!
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
传统xml配置
<server>
<port>8081<port>
server>
yaml配置:
server:
port: 8081
说明:语法要求严格!
yaml 可以保存复杂的数据结构,这是 properties 无法比拟的
k: v
注意:
“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: “kuang \n shen” 输出 :kuang 换行 shen
‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen
#对象、Map格式
k:
v1:
v2:
在下一行来写对象的属性和值得关系,注意缩进;比如:
student:
name: qinjiang
age: 3
也可以写成行内写法
student: {name: qinjiang,age: 3}
用 - 值表示数组中的一个元素,比如:
pets:
- cat
- dog
- pig
行内写法
pets: [cat,dog,pig]
小结:
yaml 文件更强大的地方在于,他可以给我们的实体类直接注入匹配值
创建实体类
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog {
private String name;
private Integer age;
}
@Component
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private Boolean isHappy;
private Date birth;
private Map<String,Object> map;
private List<Object> list;
private Dog dog;
}
之前,我们可以使用 spring 注解 @Value(“属性值”) 来给属性赋值,但是每一个都加注解赋值很麻烦
现在,我们使用 yaml 赋值属性
修改 application.xml
person:
name: swy
age: 18
isHappy: true
birth: 2020/04/08
map:
k1: v1
k2: v2
list:
- code
- music
- girl
dog:
name: huahua
age: 5
在实体类上,添加注解 @ConfigurationProperties,用前缀指定添加的是哪个属性
这个注解可能会产生提示,他表示如果我们加这个注解,但没有响应的属性配置,就会报错,如果添加了,可以忽略不管
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean isHappy;
private Date birth;
private Map<String,Object> map;
private List<Object> list;
private Dog dog;
}
启动测试类,测试,注意,这个测试类需要满足官方规范(包结构、注解)才能生效,如果手动添加需要注意
@SpringBootTest
class Springboot01HelloworldApplicationTests {
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
注意:
官方推荐使用yaml配置,当然,也可以使用 properties配置
properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;
settings–>FileEncodings 中配置;
使用举例:
添加配置文件,person.properties
name=swy
age=18
sex=男
在实体类上添加注解,指定配置文件路径,并且必须在指定的属性上加注解@Value和spel表达式
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@PropertySource(value = "classpath:person.properties")
public class Person {
@Value("${name}")
private String name;
private Integer age;
private Boolean isHappy;
private Date birth;
private Map<String,Object> map;
private List<Object> list;
private Dog dog;
}
spel表达式比较灵活,可以在很多处使用,比如,yaml 配置,
重新将配置文件切换为 yaml ,添加 spel 表达式
person:
name: swy${random.uuid}
age: ${random.int}
isHappy: true
birth: 2020/04/08
map:
k1: v1
k2: v2
list:
- code
- music
- girl
dog:
name: huahua
age: 5
注入到实体类上,看效果
spel 表达式功能比较多,可以在很多处使用,可以自行上网查询,
可以写在 yaml 中,这也说明了 yaml 的功能强大
结论:
Springboot 中可以用 @Validated 来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。
用法:
我们这里来写个注解让我们的name只能支持Email格式;
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
有了这一层校验,当我们在实际业务中注入的属性不符合校验规则时,就会自动报异常,这比我们手动去写代码校验串格式要方便很多
尤其是在接收前端数据时,使用起来非常方便
JSR303 校验提供了很多种校验规则
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则,
.......等等
除此以外,我们还可以自定义一些数据校验规则
其中,有了 @Pattern 正则判断,基本上等同于可以对任何数据进行校验了
springboot 配置文件默认在 resources目录下,此外,还可以放在以下目录中,
1. file:./config/
2. file:./
3. classpath:/config/
4. classpath:/ 这个路径,就是我们创建项目默认的配置文件存放位置
springboot 启动会扫描以上位置的 properties 或者 yml 文件作为Spring boot的默认配置文件,注意,不论放在那里,名字都必须是 application
file 指项目根路径,classpath指类路径,也就是java或resources路径下,
通过测试,我们发现,核心配置文件在这些目录的加载优先级为:1>2>3>4
我们可以在目录中配置多个环境(比如,开发、测试、生产)的配置文件,
通过命令行的方式或者 spring.profile.active 选择启动哪套配置,比如
默认启动,当然是使用 application.properties 配置,优先级最高
我们在 application.properties 中添加配置,选择激活哪个环境,比如选择dev环境
spring.profiles.active=dev
yml 更加强大,在一个配置文件中就可以实现多文档模块的功能
yml 使用 ---
分隔多文档环境,我们可以理解为这就是多个不同的配置文件,集中放在一起,使用---
分隔开,
使用 spring.profiles 分别给不同模块起名字,使用 spring.profiles.active 选择使用哪个配置启动,比如,
server:
port: 8081
spring:
profiles: dev
---
server:
port: 8082
spring:
profiles: test
---
server:
port: 8083
spring:
profiles:
active: pro
当然,当配置非常多的时候,内容过多,不好查找,也是会配置多个配置文件的,实际工作灵活使用
注意:
扩展:
指定位置加载配置文件
我们还可以通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
首先从 @SpringBootApplication
进入,一直找到 @Import(AutoConfigurationImportSelector.class)
进入,
其中 getCandidateConfigurations
方法中有 SpringFactoriesLoader
,进入
我们可以看到 spring.factories 的位置信息
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
这也就是 spring-boot-autoconfigure 依赖包下的 spring.factories 文件,
打开,可以看到很多的类,所有 xxxAutoConfiguration 都是自动配置类
而我们可以在 application 上配置的信息,和 spring.factories 目录中的配置类 xxxAutoConfiguration 有很大关联
我们以 HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;
分析其代码信息
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration
//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class})
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
//......
}
这里的信息决定了我们可以在 application 配置文件中,添加的相关配置信息可以写什么内容,比如
进入到 serverProperties.class 中可以看到,
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
好,那么,我们在 application 配置文件中就可以写 server.xxx.xxx
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
这就是自动装配的原理!
精髓
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
我们怎么知道哪些自动配置类生效?
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
在 application.properties 中添加
#开启springboot的调试类
debug=true
或者 yml 添加
debug: true
Positive matches:(自动配置类启用的:正匹配)开启生效了
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)没有生效的
Unconditional classes: (没有条件的类)
我们也可以通过这种方法,测试导入一个依赖,究竟都有哪些配置生效了
遇到不认识的配置,我们可以点进去,找到 xxxxProperties,进而反推 factories 中配置的 xxxxAutoConfigurartion,分析 xxxxAutoConfigurartion源码,判断其配置信息
出现爆红说明没有依赖,那么我们再通过 spring-boot-starter 启动器,添加响应的依赖使其生效,这是学习方法
6/7/8/9
创建 springboot web 项目,我们要充分利用其自动装配的特点
使用SpringBoot的步骤:
思考:
重点关注:
要解决的问题:
暂时添加web模块依赖,其他的需要的时候再添加
创建项目后,删掉多余的文件,保持界面干净,添加常用包结构,注意放在主启动类所在的包路径下
添加controller
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello springboot!";
}
}
访问测试:http://localhost:8080/hello
访问正常,说明基本项目创建成功
思考:
创建项目之初,resources目录默认有两个文件夹,static存放静态资源,template存放模板,
具体该如何使用,利用前面所学知识,我们查看源码 WebMvcAutoConfiguration
在源码中找到 WebMvcAutoConfigurationAdapter
这是静态资源适配器,里面有一个方法叫 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();
// webjars 配置
if (!registry.you("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
// 静态资源配置
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
"classpath:/META-INF/resources/webjars/"
路径下找资源;什么是 webjars ,相当于将web静态资源以jar包方式引入,在webjars官网中,我们可以发现,主流的web资源都有maven坐标,
官网:https://www.webjars.org
比如,我们在pom中引入jquery依赖,
<dependency>
<groupId>org.webjarsgroupId>
<artifactId>jqueryartifactId>
<version>3.6.0version>
dependency>
那么在项目依赖库中我们就可以看到相关依赖,Webjars本质就是以jar包的方式引入我们的静态资源
而"classpath:/META-INF/resources/webjars/"
路径指向的也就是这里,这样就是通过webjars的方式添加了相关静态资源
通过webjars网站引入的静态资源都符合这种结构,可以引入使用
我们只需访问 /webjars/**
目录,就会映射到 classpath:/META-INF/resources/webjars/
目录下,我们启动测试一下,
比如访问,根据我们的目录结构,我们访问:http://localhost:8080/webjars/jquery/3.6.0/jquery.js
this.resourceProperties.getStaticLocations()
表示从这里获取静态资源,我们进入查看,可以找到 WebMvcProperties
类中有private String staticPathPattern = "/**";
,这就表示在 /**
当前目录可以找到静态资源,加上
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
一共五个位置,都支持自动找到对应的静态资源
也就是访问 localhost:8080/ 添加后面的这几个位置路径下的文件名,就可以找到静态资源
其中
"classpath:/META-INF/resources/"
对应webjars目录"classpath:/static/"
已经默认存在"classpath:/resources/"
"classpath:/public/"
默认没有,我们也可以在resources目录下再创建 resources 和 publicclasspath
根目录下也能被直接访问举例,我们在 static 目录下创建 hello.html,访问:http://localhost:8080/hello.html
当然,这个几个目录也有访问优先级,我们可以使用同名文件,分别放在这个目录下,测试优先级
resources(我们自己创建的)> static > public
通常,public存放公共资源比如js,static存放图片,resources存放上传文件upload,实际情况看个人需求
比如,我们在 application.properties 中配置了
spring.resources.static-locations=classpath:/coding/,classpath:/kuang/
那么其他默认位置都失效,只有将静态资源存放在配置目录下,才可以被访问,
当然,通常我们不自定义,用默认位置就足够了
在springboot中,我们默认使用以下方式处理静态资源
localhost:8080/webjars/
localhost:8080/
优先级:
自定义:
了解新技术规则一定要学会读官网文档,读源码,而不是依赖老师讲解,这样才能提高更快
还是查看 WebMvcAutoConfiguration
源码,其中 WelcomePageHandlerMapping
表示欢迎页面的处理映射
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
return welcomePageHandlerMapping;
}
this.mvcProperties.getStaticPathPattern()
表示可以使用自定义,也可以从默认的 this.getWelcomePage
映射
private Optional<Resource> getWelcomePage() {
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
// ::是java8 中新引入的运算符
// Class::function的时候function是属于Class的,应该是静态方法。
// this::function的funtion是属于这个对象的。
// 简而言之,就是一种语法糖而已,是一种简写
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
// 欢迎页就是一个location下的的 index.html 而已
private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}
这说明,在静态资源目录下(public,resources,static,templates),我们只要放置 index.html 文件,就会成为我们的默认首页
如果是templates下必须通过controller跳转
添加 index.html 文件,访问测试:http://localhost:8080/
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
我是首页
body>
html>
通常,我们更倾向于通过 controler 跳转方式访问首页,这就必须将 index.html 放在templates目录下
所有controller跳转的页面,都必须放在templates目录下
准备 controller 和 方法,将index.html移动到templates目录下
@Controller
public class IndexController {
@RequestMapping("/swy")
public String index() {
return "index";
}
}
访问测试:http://localhost:8080/swy
为什么会访问无效呢?
因为这是之前spring mvc中访问 .jsp 页面的方式,spring mvc中访问 .jsp 页面是需要视图解析的
在 SpringBoot 中,我们可以通过模板引擎访问页面,后面讲解
与其他静态资源一样,Spring Boot在配置的静态内容位置中查找 favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。
#关闭默认图标
spring.mvc.favicon.enabled=false
自己放一个图标在静态资源目录下,我放在 public 目录下
清除浏览器缓存!刷新网页,发现图标已经变成自己的了!
新版将这个功能去掉了
首先前端给我们准备的是htlm页面
在以往的开发中,我们需要将其转为jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。jsp支持非常强大的功能,包括能写Java代码
但现在我们使用了SpringBoot之后,项目使用jar包,没有war包的web目录结构,且使用了内置的嵌入式Tomcat,因此springboot是不支持jsp页面的
那么我们该如何使用动态页面呢?SpringBoot推荐使用模板引擎 Thymeleaf
其实jsp也是一种模板引擎,模板引擎的种类很多,包括以前的 freemarker,现在 springboot 推荐的 thymeleaf,不管怎样,他们的实现思想都是一样的
三种方式:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
观察发现,这个坐标引入了以下依赖包
有些低版本的springboot是分开引入这些jar包的
要想导入资源,一定会用到 properties
查看源码 ThymeleafProperties
由此可见,Thymeleaf 的导入导出资源,前缀也就是路径必须为 "classpath:/templates/"
中,后缀必须为 ".html"
我们返回页面时,用字符串返回页面名字即可,模板引擎自动拼接字符串
templates中创建index.html页面,controller添加方法
@Controller
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "index";
}
}
使用小结:
一定查看官方文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
我们挑一些重点讲解
准备controller方法,thymeleaf导入数据依旧使用视图 Model
@RequestMapping("/t1")
public String test1(Model model){
//存入数据
model.addAttribute("msg","Hello,Thymeleaf");
return "test";
}
准备html模板页面,这里 html根标签添加了thymeleaf约束
th:
是Thymeleaf 的特有指令
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>thymeleaftitle>
head>
<body>
<h1>测试thymeleaf显示动态数据h1>
<div th:text="${msg}">div>
body>
html>
小结:
th:
加元素名使用表达式 @RequestMapping("/t1")
public String test1(Model model){
//存入数据
model.addAttribute("msg","Hello,Thymeleaf
");
return "test";
}
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>thymeleaftitle>
head>
<body>
<div th:text="${msg}">div>
<div th:utext="${msg}">div>
body>
html>
访问测试,相同的内容msg,一个被转义,一个不被转义
与vue的用法理解类似,
<h3 th:each="user:${users}" th:text="${user}">
th:each="user:${users}"
表示从users中取值,遍历,每个元素的值为user,
th:text="${user}"
表示每次循环显示的内容为${user},这个user就是遍历得来的每个元素
controller 方法
@RequestMapping("/t2")
public String test2(Model model){
//存入数据
model.addAttribute("users", Arrays.asList("jack", "mike", "rose"));
return "test2";
}
页面
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>thymeleaftitle>
head>
<body>
<h3 th:each="user:${users}" th:text="${user}">h3>
body>
html>
访问测试
也可以写成另一种方式遍历,将内容写在行内,效果都是一样的
<h3 th:each="user:${users}">[[${user}]]h3>
重难点:springboot 对 spring mvc 是如何进行自动装配的
方法:
官网文档概述:
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
The auto-configuration adds the following features on top of Spring’s defaults:
Inclusion of ContentNegotiatingViewResolver
and BeanNameViewResolver
beans.
包含视图解析器,
Support for serving static resources, including support for WebJars (covered later in this document)).
支持访问 静态资源,webjars资源
Automatic registration of Converter
, GenericConverter
, and Formatter
beans.
自动注册类型转换器,比如,前端提交单个参数,后台可以使用对象自动封装,Formatter
指日期格式转换
Support for HttpMessageConverters
(covered later in this document).
支持HTTP中的请求和响应中的message消息,比如转为json
Automatic registration of MessageCodesResolver
(covered later in this document).
支持一些消息代码,比如错误消息代码
Static index.html
support.
支持首页映射
Custom Favicon
support (covered later in this document).
支持图标自定义
Automatic use of a ConfigurableWebBindingInitializer
bean (covered later in this document).
支持web数据的初始化绑定
分割线
如果要保留这些Spring Boot MVC
定制并进行更多的MVC定制(拦截器,格式化程序,视图控制器和其他功能),则可以创建自己WebMvcConfigurer
类,使用注解@Configuration
,但不添加 @EnableWebMvc
。
如果你想提供的定制情况RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
或者ExceptionHandlerExceptionResolver
,仍然保持弹簧引导MVC自定义,你可以声明类型的豆WebMvcRegistrations
,并用它来提供这些组件的定制实例。
如果你想利用Spring MVC
中的完全控制,你可以添加自己的@Configuration
注解为@EnableWebMvc
,或者添加自己的@Configuration
-annotatedDelegatingWebMvcConfiguration
中的Javadoc中所述@EnableWebMvc
。
Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
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. If you wish to provide
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.
添加config包,创建自定义配置类,添加注解 @Configuration,实现接口 WebMvcConfigurer
我们可以通过重写方法,实现spring mvc功能扩展
@Configuration //保持springmvc现有功能,且进行扩展
public class MyMvcConfigure implements WebMvcConfigurer {
}
之前我们使用 spring mvc 时,手动配置视图解析器,使用了thymeleaf之后,默认页面走到了templates目录下,使用了模板引擎
官方说支持 ContentNegotiatingViewResolver
这个视图解析器,我们查看源码,发现他实现了ViewResolver
视图解析器接口
这是springboot帮我们自动实现的,ContentNegotiatingViewResolver
重写了这个方法
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
含义是,获取候选视图,得到最好的视图,如何获取最好的视图,我们进入getCandidateViews
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
for (MediaType requestedMediaType : requestedMediaTypes) {
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
for (String extension : extensions) {
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
代码含义,使用工具类遍历所有的ViewResolver
视图解析器,添加到候选视图解析器中,最后返回出去,工具类也就是从spring bean中获取的
结论:
我们在MyMvcConfigure
添加一个静态内部类,静态内部类实现视图解析接口,重写其方法,再添加一个方法可以返回我们的视图解析对象,并且放入bean中
@Configuration //保持springmvc现有功能,且进行扩展
public class MyMvcConfigure implements WebMvcConfigurer {
@Bean
public ViewResolver myViewResolver() {
return new MyViewResolver();
}
// 实现了ViewResolver接口,我们就把他看做视图解析器
public static class MyViewResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
}
这样,我们在mvc配置类MyMvcConfigure
中注册了一个视图解析器MyViewResolver
的bean,这个自定义的视图解析器MyViewResolver
就会生效,我们可以在 DispatcherServlet
中对doDispatch
方法断点
启动springboot,访问:http://localhost:8080/
在当前对象this
中我们可以发现,除了官方提供的视图解析器,我们还发现了自己刚配置的视图解析器
因此,如果想diy一些定制化功能,我们只需要写一些组件,交给springboot,springboot就会自动装配
在WebMvcAutoConfiguration
中找到格式化转换器
@Bean
@Override
public FormattingConversionService mvcConversionService() {
// 拿到配置文件中的格式化规则
WebConversionService conversionService =
new WebConversionService(this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
点进去
public String getDateFormat() {
return this.dateFormat;
}
/**
* Date format to use. For instance, `dd/MM/yyyy`. 默认的
*/
private String dateFormat;
这说明可以在我们的Properties文件中,我们可以进行自动配置它!
如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:
默认的格式就是注释里面的dd/MM/yyyy
,我们可以在配置文件properties中自定义格式,比如,
spring.mvc.format.date=dd-MM-yyyy
这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。
SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!
添加一个MyMvcConfigure1
,重写方法,实现自定义视图跳转
@Configuration
public class MyMvcConfigure1 implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/swy").setViewName("/index");
}
}
他表示,我们访问/swy
就会跳转index
页面,访问测试一下 http://localhost:8080/swy
这种配置之前是需要在配置文件中进行配置的,而现在只需在自定义WebMvcConfigurer
中进行
如果我们要扩展springmvc,官方推荐我们用这种方式
为什么自定义的配置会生效?
这个父类中有这样一段代码:
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 从容器中获取所有的webmvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
}
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
public void addViewControllers(ViewControllerRegistry registry) {
Iterator var2 = this.delegates.iterator();
while(var2.hasNext()) {
// 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
delegate.addViewControllers(registry);
}
}
结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;
为什么接管mvc时候不能使用注解@EnableWebMvc
?
在MyMvcConfigure1
上添加这个注解,点击进入,可以看到
他导入了一个类 DelegatingWebMvcConfiguration
查看他的源码可以知道,这个类可以从容器中获取所有的webmvcconfig
我们再看WebMvcAutoConfiguration
这表明,只有当容器中没有WebMvcConfigurationSupport
时,WebMvcAutoConfiguration
自动配置才会生效。
而DelegatingWebMvcConfiguration
继承了WebMvcConfigurationSupport
所以,当我们使用注解@EnableWebMvc
时,整个自动配置WebMvcAutoConfiguration
都将失效,只剩下我们自己的配置
官方文档:
If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.
当然,我们开发中,不推荐使用全面接管SpringMVC
总结:
@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了
而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能
注意:
xxxConfiguration
帮助我们进行了扩展配置,因为它可能改变了spring原有的配置,或者扩展,覆盖了原始的功能,准备素材,下载地址:https://download.csdn.net/download/weixin_47257749/16617739
创建新项目springboot
选择版本,添加依赖
删除多余文件,保持界面整洁
测试启动成功,确保项目创建成功
导入,页面素材,css/js/img 放在 static 目录下,html放在templates目录下
添加项目包结构,pojo/service/controller/dao
实体类
// 部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
// 员工表
@Data
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;// 0:女 1:男
private Department department;
private Date birth;
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.department = department;
this.birth = new Date();// 日期自动生成
}
}
dao层,这里我们暂不使用数据库,而是在 dao 层使用假数据
// 部门dao
public class DepartMentDao {
// 模拟数据
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> getDepartments() {
return departments.values();
}
public Department getDepartmentById(Integer id) {
return departments.get(id);
}
}
// 员工dao
@Repository
public class EmployeeDao {
// 模拟数据
private static Map<Integer, Employee> employees = null;
@Autowired
private DepartMentDao departMentDao;
static {
employees.put(1001, new Employee(1001, "mike", "[email protected]", 0, new Department(101, "教学部")));
employees.put(1002, new Employee(1002, "jack", "[email protected]", 0, new Department(102, "时长部")));
employees.put(1003, new Employee(1003, "rose", "[email protected]", 0, new Department(103, "运营部")));
employees.put(1004, new Employee(1004, "bob", "[email protected]", 0, new Department(104, "后勤部")));
employees.put(1005, new Employee(1005, "John", "[email protected]", 0, new Department(105, "小卖部")));
}
// 增删改查
private static Integer initId = 1006;
public void save(Employee employee) {
if (employee.getId()==null) {
employee.setId(initId++);
}
employee.setDepartment(departMentDao.getDepartmentById(employee.getDepartment().getId()));
employees.put(employee.getId(), employee);
}
public Collection<Employee> getEmployees() {
return employees.values();
}
public Employee getEmployeeById(Integer id) {
return employees.get(id);
}
public void deleteEmployeeById(Integer id) {
employees.remove(id);
}
}
添加 controller 跳转首页
@Controller
public class IndexController {
@RequestMapping({"/","/index.html"})
public String index() {
return "index";
}
}
启动测试
我们也可以在mvc配置类中,通过修改拦截器完成跳转,
@Configuration
public class MyMvcConfigure implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
就使用这个方法了,可以去掉上面的controller方法
此时的页面仅为html静态页面,不能显示数据,而且我们要解决样式问题,
开始引入thymeleaf,修改index.html,
参考官网模板:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#using-texts
xmlns:th="http://www.thymeleaf.org"
@{}
就相当于classpath:
目录
访问测试,如果页面仍然没有样式,可能是模板引擎缓存所致,可添加配置,关闭模板引擎,清理浏览器缓存,重启
spring.thymeleaf.cache=false
<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">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstraptitle>
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/css/signin.css}" rel="stylesheet">
head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal">Please sign inh1>
<label class="sr-only">Usernamelabel>
<input type="text" class="form-control" placeholder="Username" required="" autofocus="">
<label class="sr-only">Passwordlabel>
<input type="password" class="form-control" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
label>
div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign inbutton>
<p class="mt-5 mb-3 text-muted">© 2017-2018p>
<a class="btn btn-sm">中文a>
<a class="btn btn-sm">Englisha>
form>
body>
html>
修改其他页面的链接标签,先确保页面样式正常显示
404页面,将所有本地链接修改成thymeleaf格式,在线cdn链接不要动
同理,修改 dashboard,list 页面样式的链接格式
我们也可以利用前面所学知识,自定义添加资源访问目录,比如,测试使用,其实不加
server.servlet.context-path=/swy
小结:
很多网站都有中英文切换,这就是国际化,需要我们进行适配
首先确保 idea设置中 file encodings 中所有编码为 utf-8,否则将会出现乱码
在resources 目录添加文件夹 i18n,i18n是国际化的缩写,习惯性写法
在 i18n 目录下创建两个配置文件 login.properties 和 login_zh_CN.properties,我们看到系统会自动合并在一个目录下
我们可以在这个文件夹上添加新的国家化配置,比如,login_en_US.properties
注意配置名称必须按照规范格式来写,写完直接ok
IDEA提供了可视化配置,我们可以使用
打开 login_en_US.properties,下方点击打开可视化配置
添加 login.tip
此时 login,properties页面自动新增,login.tip= tip是键名,我们自己定
login.properties是默认显示,其他配置是对应的国际化配置
在可视化配置中,我们将需要国际化的词句填写此处,并翻译语言放在相应的语言分类中,比如
每添加一组国际化对应关系,就要给login创建一个子文件,比如,密码
用这种方法,我们多创建一些国际化配置
注意,国际化不是乱配置的,我们要参考当前页面中的单词来进行配置,大小写也要一致
国际化的配置规则,我们可以查看源码 MessageSourceAutoConfiguration
还需要让springboot能够读取到国际化配置文件,因此需要添加springboot配置
spring.messages.basename=i18n.login
参考官网文档,修改各处需要国际化的位置
<h1 class="h3 mb-3 font-weight-normal">Please sign inh1>
改为
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
重启,测试一下
可以看到,修改成功,将剩下的国家化配置,修改到对应的文字所在的标签上,注意我们这里只能将标签内容国际化,标签属性不算
不同的类型的标签,有不同的修改方式
剩余的自行修改
配置好了中英文对照,我们该如何切换?
在页面上我们应该通过点击来进行切换,我们要在后台提供切换的请求的处理
通过官网和源码推测得知,WebMvcAutoConfiguration
中的localeResolver
方法用来处理国际化视图解析,里面定义了国际化消息类型的转换,localeResolver
方法有关于国际化的介绍,大致含义就是,用户有配置就用用户的,没有就用默认的
@Override
@Bean
@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
@SuppressWarnings("deprecation")
public LocaleResolver localeResolver() {
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.webProperties.getLocale());
}
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
: this.mvcProperties.getLocale();
localeResolver.setDefaultLocale(locale);
return localeResolver;
}
默认的就是
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
而AcceptHeaderLocaleResolver
则实现了LocaleResolver
接口,
只要实现 LocaleResolver
接口,就是一个国际化配置,因此我们可以自己写一个
在config中添加一个自定义 LocaleResolver
public class MyLocaleResolver implements LocaleResolver {
// 解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
return null;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
通过切换按钮的发送请求,在这个类上重写方法(参照父级方法的样式写),拦截请求,切换国际化
修改中英文请求标签,thymeleaf中请求参数直接用()
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">Englisha>
修改 MyLocaleResolver
的重写方法,拦截请求切换国际化
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("_");
// 拆串获得国家地区
return new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
写完国际化配置,我们要将其返回的 Locale
放到配置类WebMvcAutoConfiguration
中,注册为bean
@Configuration
public class MyMvcConfigure implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
// 自定义的国际化组件生效
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
}
注意方法名不能变,因为这里的方法必须按照WebMvcConfigurer
规定的来写,其实就是在重写WebMvcAutoConfiguration
的方法
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model) {
// 具体业务,这里我们规定用户名随意,密码为123456
if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
return "redirect:/main.html";
} else {
model.addAttribute("msg", "用户名或密码错误");
return "index";
}
}
}
<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">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstraptitle>
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/css/signin.css}" rel="stylesheet">
head>
<body class="text-center">
<form class="form-signin" th:action="@{/user/login}">
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p>
<label class="sr-only" th:text="#{login.username}">Usernamelabel>
<input type="text" class="form-control" th:name="username" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Passwordlabel>
<input type="password" class="form-control" th:name="password" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
label>
div>
<button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]button>
<p class="mt-5 mb-3 text-muted">© 2017-2018p>
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">Englisha>
form>
body>
html>
页面跳转:
@Configuration
public class MyMvcConfigure implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("main.html").setViewName("dashboard");
}
// 自定义的国际化组件生效
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
}
访问测试:
访问失败,提示消息只有在访问失败的时候才会显现
访问成功,显示当前页面为main.html,实际上这是 dashboard.html页面
目前已经登录成功,但是还是能直接访问后台页面的,因此我们需要判断用户是否登录,这就要拦截器
我们自定义拦截器 LoginHandlerInterceptor
实现接口 HandlerInterceptor
public class LoginHandlerInterceptor 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.html").forward(request, response);
return false;
} else {
return true;
}
}
}
WebMvcConfigurer
中,通过方法名推测到配置拦截的方法名,添加该方法返回我们的拦截器,@Configuration
public class MyMvcConfigure implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("main.html").setViewName("dashboard");
}
// 自定义的国际化组件生效
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
// 自定义拦截器生效
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index.html","/user/login","/css/**","/js/**","/img/**");
}
}
修改controller,添加session,这里我们伪造的所谓的session,其生命周期整个项目的运行期
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model, HttpSession session) {
// 具体业务,这里我们规定用户名随意,密码为123456
if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
session.setAttribute("loginUser", username);
return "redirect:/main.html";
} else {
model.addAttribute("msg", "用户名或密码错误");
return "index";
}
}
}
这样,如果没有登录就访问后台页面,也会跳转会登录页面
给 首页dashboard左上角添加登录名
将 dashboard.html页面中
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company namea>
修改为
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]a>
先将首页dashboard.html所有加载本地的链接改造成thymeleaf可以识别的格式,确保页面正常展示
在dashboard页面,我们将以下代码片段修改,用于跳转list页面
ps:svg标签展现的其实是个图标
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">path>
<circle cx="9" cy="7" r="4">circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87">path>
<path d="M16 3.13a4 4 0 0 1 0 7.75">path>
svg>
Customers
a>
li>
修改为
<li class="nav-item">
<a class="nav-link" th:href="@{/emps}">
员工管理
a>
li>
将Customers改为员工管理,点击员工管理,后台接收请求,显示列表,添加controller处理请求
@Controller
public class EmployeeController {
// 简化项目,我们直接调用dao
@Autowired
private EmployeeDao employeeDao;
@RequestMapping("/emps")
public String showEmployeeList(Model model) {
Collection<Employee> employees = employeeDao.getEmployees();
model.addAttribute("emps", employees);
return "emp/list";
}
}
templates添加目录emp,将 list.html 页面移至emp目录下
现在我们面临一个问题,现在我们点击员工管理超链接,并未显示高亮,无法正常跳转
修改list页面
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">path>
<circle cx="9" cy="7" r="4">circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87">path>
<path d="M16 3.13a4 4 0 0 1 0 7.75">path>
svg>
Customers
a>
li>
修改为,
<li class="nav-item">
<a class="nav-link" th:href="@{/emps}">
员工管理
a>
li>
list这段代码要和dashboard页面的这段代码对应上,这是网页模板本身的规范,不必深究
这样,点击首页 dashboard 的员工管理菜单,页面将会跳到 list 页面
通过观察我们发现,首页和员工页面的侧边栏、上边栏都是一样的,因此我们可以想办法,将公共组件复用
在 thymeleaf 文档中有组件化的功能
大致方法为,定义id,抽取公共页面,插入到指定的位置
通过分析,我们发现首页 dashboard 的以下位置为顶部栏、侧边栏模块
将 dashboard 的侧边栏标签修改
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
修改为
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
这样,我们将首页的侧边栏抽取出来了,成为一个组件
删掉 list 页中的侧边栏模块,将首页抽取的侧边栏放到这里
打开 list 代码,删除以下片段
并在原位置上添加代码,插入组件,insert="~{}"
是thymeleaf的规范写法,表示插入组件
dashboard::sidebar
表示dashboard页面中的sidebar
组件
<div th:insert="~{dashboard::sidebar}">div>
使用同样的方式,将首页的上边栏也抽取,暂时插入到list页面,删掉list原有的上边栏
这样 list 页面的上边栏、侧边栏 就来源于首页的公共组件,测试看效果
测试成功后,我们进一步提取,干脆将组件独立成一个页面,存放在公共目录 common 下 ,改造,body和head标签都省略了,修改一些内容变成
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign outa>
li>
ul>
nav>
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
中间内容不变,省略...
nav>
html>
然后首页dashboard与list列表引用这里的组件,修改一下标签,在需要使用的位置上换成
<div th:insert="~{common/common::topbar}">div>
和
<div th:insert="~{common/common::sidebar}">div>
这样,我们统一修改公共部分,所有的使用为都会发生变化
我们将几处位置英文修改为中文,后续自行国际化处理吧,
逻辑分析:
解决办法:
因此,在 common 页面中,将两个菜单 a 标签分别做各自的参数判断,是否高亮
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
在 list 页面上的侧边栏组件应该携带参数,参数能让员工管理高亮
<div th:insert="~{common/common::sidebar(active='list.html')}">div>
在 dashboard 页面上的侧边栏组件应该携带参数,参数能让首页高亮
<div th:insert="~{common/common::sidebar(active='main.html')}">div>
list 页面可以显示员工列表,
<table class="table table-striped table-sm">
...
table>
因此我们需要改造标签内容
<table class="table table-striped table-sm">
<thead>
<tr>
<th>idth>
<th>lastNameth>
<th>emailth>
<th>genderth>
<th>departmentth>
<th>birthth>
<th>operateth>
tr>
thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}">td>
<td th:text="${emp.getLastName()}">td>
<td th:text="${emp.getEmail()}">td>
<td th:text="${emp.getGender()==0?'female':'male'}">td>
<td th:text="${emp.department.getDepartmentName()}">td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}">td>
<td>
<button class="btn btn-sm btn-primary">updatebutton>
<button class="btn btn-sm btn-danger">deletebutton>
td>
tr>
tbody>
table>
业务分析:
打开list.html,在 列表上方添加按钮,
<h4><a class="btn btn-sm btn-success" th:href="@{/toAdd}">添加员工a>h4>
修改 EmployeeController,添加方法
@RequestMapping("/toAdd")
public String toAddpage() {
return "emp/add";
}
在emp目录下新增add.html页面,我们复制list页面即可,修改里面的 main 标签里的内容
我们可以在bootstrap官网找一些表单模板拷贝过来,进行改造,
确保每一个表单里都有name属性,并且属性名与实体类属性名一一对应
其中,部门信息的表单是一个下拉框,我们要实现点击下拉框可以选择部门信息,因此在跳转到当前页面的时候,就已经完查询并携带了部门信息,
修改 EmployeeController,新增注入属性,修改toAdd方法
@Autowired
public DepartmentDao departmentDao;
@RequestMapping("/toAdd")
public String toAddpage(Model model) {
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);
return "emp/add";
}
修改下拉框,能够动态显示部门信息
这里需要注意,单个表单无法提交对象(department),所以这里name对应一个属性(department.id),
<select class="form-control" name="department.id">
<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
th:value="${dept.getId()}">option>
select>
测试,确保,页面能跳转,可以正确显示列表并选择部门
给form标签添加提交数据的请求地址
<form class="form-horizontal" th:action="@{/add}" method="post">
在 EmployeeController 中添加方法 接收表单数据
ps:ThymeleafViewResolver 中有定义视图解析器的规则,包括请求转发和重定向,因此我们可以在这里使用转发、重定向
@RequestMapping("/add")
public String addEmp(Employee employee) {
employeeDao.save(employee);
return "redirect:/emps";
}
注意,我们提交的时间格式必须满足,1993/2/8 这样的格式,这是默认格式,如果需要提交其他格式,需要在springboot配置文件中,自定义提交的时间格式,比如
spring.mvc.date-format=yyyy-MM-dd
修改页可以和添加页面共用一个页面,这里我们暂时简化技术实现,使用一个新页面用于修改
list 页面 修改按钮,添加跳转链接,同时携带当前行的 id
<button class="btn btn-sm btn-primary">updatebutton>
修改为
<a class="btn btn-sm btn-primary" th:href="@{/toUpdate(id=${emp.getId()})}">updatea>
EmployeeController 添加方法接收请求,跳转到修改页面,携带当前行信息,返回前端
由于页面有部门信息下拉框,所以还要携带部门信息
@RequestMapping("/toUpdate")
public String toUpdateEmp(@PathVariable("id") Integer id, Model model) {
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("emp", employee);
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);
return "emp/update";
}
在emp目录中,复制一个add页面,改名为update,开始改造修改页面,修改要点:
<form class="form-horizontal" th:action="@{/update}" method="post">
<input type="hidden" name="id" th:value="${emp.getId()}">
<div class="form-group">
<label class="col-sm-2 control-label">名字label>
<div class="col-sm-10">
<input th:value="${emp.getLastName()}" type="text" class="form-control" placeholder="张三" name="lastName">
div>
div>
<div class="form-group">
<label class="col-sm-2 control-label">邮件label>
<div class="col-sm-10">
<input th:value="${emp.getEmail()}" type="email" class="form-control" placeholder="[email protected]" name="email">
div>
div>
<div class="form-group">
<label class="col-sm-2 control-label">性别label>
<div class="col-sm-offset-2 col-sm-10">
<label>
<input th:checked="${emp.getGender()==1}" type="radio" name="gender" checked value="1"> 男
label>
<label>
<input th:checked="${emp.getGender()==0}" type="radio" name="gender" value="0"> 女
label>
div>
div>
<div class="form-group">
<label class="col-sm-2 control-label">部门label>
<div class="col-sm-10">
<select class="form-control" name="department.id">
<option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
th:value="${dept.getId()}">option>
select>
div>
div>
<div class="form-group">
<label class="col-sm-2 control-label">生日label>
<div class="col-sm-10">
<input th:value="${#dates.format(emp.getBirth(),'yyyy/MM/dd')}" type="text" class="form-control" name="birth">
div>
div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-sm btn-success" type="submit">添加button>
div>
div>
form>
EmployeeController 添加方法接收修改请求,然后重定向跳转会员工页面
@RequestMapping("/update")
public String updateEmp(Employee employee) {
employeeDao.save(employee);
return "redirect:/emps";
}
修改删除按钮
根据id删除当前行信息,修改
<button class="btn btn-sm btn-danger">deletebutton>
修改为
<a class="btn btn-sm btn-danger" th:href="@{/delete(id=${emp.getId()})}">deletea>
EmployeeController 添加删除方法,删除完毕跳转回员工页面
@RequestMapping("/delete")
public String deleteEmp(@RequestParam("id") Integer id) {
employeeDao.deleteEmployeeById(id);
return "redirect:/emps";
}
修改公共组件页面 common.html,修改
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign outa>
修改为
<a class="nav-link" th:href="@{/user/signOut}">注销a>
LoginController 添加方法
@RequestMapping("/user/signOut")
public String signOut(HttpSession session) {
session.invalidate();
return "redirect:/index.html";
}
我们可以统一创建一个页面用于返回 404异常的页面
在templates目录中创建error目录,在其中创建404.html,请求报错的时候如果是404异常就会自动跳到这个页面,这是springboot的功能
员工管理demo,无数据库版就此完成,附上源码
https://download.csdn.net/download/weixin_47257749/16665617
方法:
对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。
Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。
Sping Data 官网:https://spring.io/projects/spring-data
数据库相关的启动器 :参考官方文档:
https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
创建 application.yml 用来数据库连接文件
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/my_study?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
由于springboot具有自动装配的特点,当我们导入 jdbc和数据库driver,springboot会自动生成一下数据库操作的对象
启动测试:
@SpringBootTest
class Springboot05DataApplicationTests {
@Autowired
DataSource dataSource;// 注入数据源
@Test
void contextLoads() throws SQLException {
// 查看默认数据源
System.out.println(dataSource.getClass());
// 获取数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 关闭
connection.close();
}
}
可以看到,springboot 默认使用 HikariDataSource 作为数据源,号称速度最快
HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;
可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。
从配置文件中点进去,可以查看 DataSourceProperties
源码,
有 properties 就一定有 autoconfiguration,
我们搜索找到,DataSourceAutoConfiguration
源码,
里面配置了datasource的详细使用配置
在springboot 中有很多 template 模板,这是springboot 配置好的 bean,我们可以拿来即用
创建 JDBCController
,在 JdbcTemplate
中有大量的方法可供我们立即使用,我们只需注入,拿来即用
没有提前准备实体类,这里简化一下,直接使用万能的map接收
@RestController
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
// 查询数据库的所有信息
// 直接用Map接收
// 原生的jdbc需要手动写sql
@RequestMapping("userList")
public List<Map<String, Object>> userList() {
String sql = "select * from user";
return jdbcTemplate.queryForList(sql);
}
}
@RequestMapping("/addUser")
public String addUser() {
String sql = "insert into user(id, name, pwd) values (7, 'honey', '654321')";
jdbcTemplate.update(sql);
return "update success";
}
@RequestMapping("/addUser")
public String addUser() {
String sql = "insert into user(id, name, pwd) values (7, 'honey', '654321')";
jdbcTemplate.update(sql);
return "update success";
}
@RequestMapping("/updateUser")
public String updateUser(int id) {
String sql = "update user set name=?,pwd=?,where id="+id;
Object[] objects = new Object[2];
objects[0] = "sweety";
objects[1] = "dommy";
jdbcTemplate.update(sql, objects);
return "update success";
}
@RequestMapping("/deleteUser")
public String deleteUser(@RequestParam("id") int id) {
String sql = "delete from user where id=?";
jdbcTemplate.update(sql, id);
return "delete success";
}
springboot帮助我们自动实现了事务
其实有了 springboot template,我们即便是使用 jdbc 也简化了大量操作
JDBCTemplate
JdbcTemplate主要提供以下几类方法:
我们常说的德鲁伊数据源,Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。
Github地址:https://github.com/alibaba/druid/
com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.21version>
dependency>
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/my_study?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源
切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码
在依赖中查看源码 DruidDataSource ,
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/my_study?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
监控功能是 Druid 最强大的功能,这里还需要们使用log4j
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
创建配置类,DruidConfig,在SpringBoot 中我们主要使用 配置类来配置,这对应了 spring 中的bean 的 xml 配置文件
添加数据源,然后我们可以自己添加配置
@Configuration
public class DruidConfig {
/*
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}
// 配置后台监控
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// 需要有人登陆
Map<String,String> initParameters = new HashMap<>();
// 添加配置,前面的配置名是固定的写法
initParameters.put("loginUsername", "admin");
initParameters.put("loginPassword", "666");
// 允许谁可以访问,空表示所有人,localhost为本机
initParameters.put("allow", "");
// 禁止谁访问
initParameters.put("mike", "192.168.11.23");
// 设置初始化参数
bean.setInitParameters(initParameters);
return bean;
}
}
启动测试,
当我们执行数据层操作的时候,后台就会有详细记录,这是 Druid 最强大的地方
配置 Druid 监控 之 web 监控的 filter
WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
由于springboot 内置了 servlet 容器,所以没有web.xml,我们必须使用替代类 ServletRegistrationBean 进行配置
我们可以在 配置类中,添加过滤器,这里就直接在已有的 DruidConfig 中配置
// filter 拦截器
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
// 设置过滤哪些请求
Map<String,String> initParameters = new HashMap<>();
// 排除掉过滤的请求,不进行统计
initParameters.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParameters);
return bean;
}
进入 WebStatFilter 我们可以查看可以进行哪些配置
官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
Maven仓库地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.1
创建springboot项目,选择依赖
导入 mybatis springboot依赖,导入 lombok 依赖了
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.1version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.16version>
<scope>providedscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.21version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
maven配置资源过滤
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.xmlinclude>
includes>
<filtering>truefiltering>
resource>
resources>
注意:mybatis-spring-boot-starter 依赖不是springboot官方提供的,所以还是要自己提供版本号
作用就是整合 mybatis 和 springboot
springboot 配置文件,我们仍然使用上一个案例的配置,
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/my_study?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private String pwd;
}
mapper接口类,添加 @Repository 注册成 bean
在springboot中,mybatis官方要求使用 @Mapper 表示本类是一个mybatis的mapper接口
也可以在主启动类上,加注解 @MapperScan 一次性扫描包
@Mapper
@Repository
public interface UserMapper {
List<User> queryList();
User queryUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUserById(int id);
}
在 resources目录下,创建mapper目录,添加 mapper.xml 映射文件
我们可以先在springboot配置文件中统一配置mybatis对应的实体类的别名,这样在映射文件中,我们可以直接使用类名,不必使用全限定类名
mybatis:
type-aliases-package: com.swy.pojo # 包起别名
mapper-locations: classpath:mybatis/mapper/*.xml # 注册映射文件
注意写法:classpath 指的 resources ,mybatis目录前不需要加 /,加 / 就表示整个项目的根目录下了
以前在 mybatis 中使用的所有配置文件,都可以在springboot配置文件中配置,具体使用方法可以直接在配置中点减去查看 properties
<mapper namespace="com.swy.mapper.UserMapper">
<select id="queryList" resultType="User">
select * from user
select>
<select id="queryUserById" resultType="User">
select * from user where id = #{id}
select>
<insert id="addUser" parameterType="User">
insert into user (id,name,pwd) values (#{id}, #{name}, #{pwd})
insert>
<update id="updateUser" parameterType="User">
update user set name = #{name}, pwd = #{pwd}, where id = #{id}
update>
<delete id="deleteUserById" parameterType="int">
delete from user where id = #{id}
delete>
mapper>
为了简化,我们省略service层,直接controller层
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/queryUserList")
public List<User> queryUserList() {
return userMapper.queryList();
}
@RequestMapping("/addUser")
public String addUser(int id) {
userMapper.addUser(new User(8, "kitty", "8852"));
return "add user success";
}
@RequestMapping("/updateUser")
public String updateUser() {
userMapper.updateUser(new User(1, "hack", "448844"));
return "update user success";
}
@RequestMapping("/deleteUser")
public String deleteUser() {
userMapper.deleteUserById(2);
return "delete success";
}
}
启动测试
springboot 默认已经帮我们实现自动装配 mybatis 事务了
在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。
如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。
因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。
在之前的学习中,我们使用 springmvc 中的 过滤器、拦截器 实现简单的安全功能,
使用安全框架功能更加强大,定制化程度高,配置方法更简单
市面上存在比较有名的:Shiro,Spring Security !
二者比较像,主要实现 认证、授权 功能
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求
对于权限 一般会细分为功能权限,访问权限,和菜单权限。
如果使用拦截器、过滤器会大量使用原生代码,代码会写的非常的繁琐,冗余。Spring Scecurity就是为此卫生
Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-java8timeartifactId>
dependency>
导入 security 的素材
添加 controller,这里我们使用了restful 风格减少了方法的使用
@Controller
public class RouterController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "views/login";
}
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id") int id){
return "views/level1/"+id;
}
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id") int id){
return "views/level2/"+id;
}
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id") int id){
return "views/level3/"+id;
}
}
接下来我们所进行的操作,添加安全框架的功能,不会对原有代码做修改,这也是AOP的思想
Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
“授权” (Authorization)
要想使用 Spring Security ,只需要添加依赖即可
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
参考官网:https://spring.io/projects/spring-security
查看我们自己项目中的版本,找到对应的帮助文档:
https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4
如果我们想创建一个 security 配置类,我们只需要写一个配置类继承 WebSecurityConfigurerAdapter
即可
根据我们需求,重写对应的父类方法,添加我们的定制化需求,比如这里我们制定了请求额授权规则
需求,首页所有人可以访问,功能也只有有权限的人可以访问
支持链式编程
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定制请求的授权规则
// 首页所有人可以访问
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
}
}
测试发现 除了首页都进不去了,因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以
403 表示权限不允许
4. 没有权限的时候,应该默认跳转到登录页面
configure() 方法修改为
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定制请求的授权规则
// 首页所有人可以访问
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
// 开启自动配置的登录功能
// login 请求来到登录页
// login?error 重定向到这里表示登录失败
http.formLogin();
我们并没有配添加接收登录请求的controller方法,,仅用配置就实现了,他表示当用户没有登录且访问了需要登录才能访问的页面时,就会自动跳转这里
点进去发现 HttpSecurity 的源码内容非常多
因此我们在templates中准备了 login.html 就会自动跳转,里面有包括定义了可以使用链式编程
测试一下:发现,没有权限的时候,会跳转到登录的页面
如何,配置登录用户,我们需要在数据库准备好数据
认证功能需要重写方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
我们进入 configure 查看
注释说明,AuthenticationManagerBuilder 这个对象可以通过链式编程,在后面.方法
的方式,选择不同的认证方式,
比如 jdbc认证,内存认证,我们现在选择了内存认证 .inMemoryAuthentication()
,所以还要在后面继续添加我们需要认证的 用户数据,包括 user,password,roles
因此,我们将认证方法重写为一下,并且我们可以通过.and
拼接多个认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中定义,也可以在jdbc中去拿....
auth.inMemoryAuthentication()
.withUser("kuangshen").password("123456").roles("vip2","vip3")
.and()
.withUser("root").password("123456").roles("vip1","vip2","vip3")
.and()
.withUser("guest").password("123456").roles("vip1","vip2");
}
注意:这种认证方式与springboot版本有可能有冲突,更新速度不均衡导致,至少 在springboot 2.1.x 中是有效地
登录测试一下
要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
spring security 官方推荐的是使用bcrypt加密方式。
将代码修改为
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中定义,也可以在jdbc中去拿....
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}
当然我们也可以使用 MD5+salt 的方式加密
测试,发现,登录成功,并且每个角色只能访问自己认证下的规则
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
//....
//开启自动配置的注销的功能
// /logout 注销请求
http.logout();
}
进入 logout 源码可以查看使用uti方式
<a class="item" th:href="@{/logout}">
<i class="address card icon">i> 注销
a>
测试注销功能
但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢
我们可以在logout后面继续链式编程,配置注销后跳转的页面
// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");
测试注销后跳转首页
我们还可以继续链式编程,指定清空cookie或者清空session
我们现在又来一个需求:用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如kuangshen这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?
实现这个需求,需要结合thymeleaf中的一些功能
sec:authorize=“isAuthenticated()”:是否认证登录!来显示不同的页面
需要添加 thymeleaf 和 security 的整合依赖
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity5artifactId>
<version>3.0.4.RELEASEversion>
dependency>
导入命名空间
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
修改导航栏,增加认证判断
<div class="right menu">
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/login}">
<i class="address card icon">i> 登录
a>
div>
<div sec:authorize="isAuthenticated()">
<a class="item">
<i class="address card icon">i>
用户名:<span sec:authentication="principal.username">span>
角色:<span sec:authentication="principal.authorities">span>
a>
div>
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="address card icon">i> 注销
a>
div>
div>
注意:可能会不生效!有版本要求,thymeleaf的这项功能最高支持 springboot 2.0.9,因为springboot 更新速度太快,所以大多数人更习惯使用 2.0.7,功能比较稳定
如果注销后出现404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,不接收 get 请求,而我们的页面默认使用的get 请求
我们可以将请求改为post表单提交,或者在spring security中关闭 csrf 功能;我们试试:在 配置中增加
http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.logout().logoutSuccessUrl("/");
让页面在不同角色下显示不同信息,动态展现菜单
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon">i> Level-1-1a>div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon">i> Level-1-2a>div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon">i> Level-1-3a>div>
div>
div>
div>
div>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon">i> Level-2-1a>div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon">i> Level-2-2a>div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon">i> Level-2-3a>div>
div>
div>
div>
div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon">i> Level-3-1a>div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon">i> Level-3-2a>div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon">i> Level-3-3a>div>
div>
div>
div>
div>
新的需求,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
//...
//记住我
http.rememberMe();
}
记住的实现原理? Cookie 实现的
我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie
结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie
现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?
http.formLogin().loginPage("/toLogin");
<a class="item" th:href="@{/toLogin}">
<i class="address card icon">i> 登录
a>
在 loginPage()源码中的注释上有写明
<form th:action="@{/login}" method="post">
<div class="field">
<label>Usernamelabel>
<div class="ui left icon input">
<input type="text" placeholder="Username" name="username">
<i class="user icon">i>
div>
div>
<div class="field">
<label>Passwordlabel>
<div class="ui left icon input">
<input type="password" name="password">
<i class="lock icon">i>
div>
div>
<input type="submit" class="ui blue submit button"/>
form>
http.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/toLogin")
.loginProcessingUrl("/login"); // 登陆表单提交请求
<input type="checkbox" name="remember"> 记住我
//定制记住我的参数!
http.rememberMe().rememberMeParameter("remember");
注意:当我们不使用默认登录页面,而是使用自己配置的登录页面的时候,前端的请求,跳转页面的请求,和后端接收地址,都要响应的进行调整,可能会出现错乱的情况,一定要详细检查,多试验
spring security 的使用并不难,难点在于版本的适配经常会出现问题,需要多尝试版本;还有默认的登录配置与自己配置的区分,避免出现混在一起错乱的情况