说明:
- 狂神说spring boot视频 哔哩哔哩:https://www.bilibili.com/video/BV1PE411i7CV?p=7&spm_id_from=pageDriver
- springboot 笔记整合:岚_枫博客整合,点击跳转
一、回顾pring
Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson 。
Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
1. Spring是如何简化Java开发的
为了降低Java开发的复杂性,Spring采用了以下4种关键策略:
-
1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
-
2、通过IOC,依赖注入(DI)和面向接口实现松耦合;
-
3、基于切面(AOP)和惯例进行声明式编程;
-
4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;
2. 什么是SpringBoot
-
学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍;
-
言归正传,什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。
-
所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。
-
是的这就是Java企业级应用->J2EE->spring->springboot的过程。
-
随着 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开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和XML配置的要求
- 真的很爽,我们快速去体验开发个接口的感觉吧!
2、第一个SpringBoot程序
环境准备
- jdk1.8
- Maven 3.6.3
- Springboot:最新版
开发工具:
- IDEA
- 官方提供了一个快速生成的网站,IDEA集成了这个网站
创建基础项目说明
- Spring官方提供了非常方便的工具让我们快速构建应用
- Spring Initializr:https://start.spring.io/
1. 项目创建方式一:使用Spring Initializr 的 Web页面创建项目
-
2、填写项目信息
-
3、点击”Generate Project“按钮生成项目;下载此项目,并解压到指定位置
-
4、用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
- File->Open->打开项目
- 打开pom,更新依赖
-
5、测试运行
- 打开文件
src/main/java/com/hello/helloword/HellowordApplication.java
- 点击倒三角运行
3. 访问路径:
http://localhost:8080/
- 打开文件
- 新建controller
- 重新运行,访问
http://localhost:8080/hello
,成功
- 停止运行,进行打包
- 使用管理员方式,打包打包文件位置,运行命令
java -jar 包名
当前是java -jar .\helloword-0.0.1-SNAPSHOT.jar
回车运行
- 访问
http://localhost:8080/hello
依旧运行成功
2. 项目创建方式二:使用 IDEA 直接创建项目
-
1、创建一个新项目 File->New->Project。
-
2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现。
-
3、填写项目信息。<爆红是因为原先创建过了,当前截图是为了写这个博客>
-
4、选择初始化的组件(初学勾选 Web 即可),点击Finish。
-
5、等待项目构建成功。
-
7、删掉多余余的东西文件
删除后的项目路径
3. 项目结构分析
通过上面步骤完成了基础项目的创建。就会自动生成以下文件。
1、程序的主启动类
2、一个 application.properties 配置文件
3、一个 测试类
4、一个 pom.xml
1. pom分析
打开pom.xml,看看Spring Boot项目的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 当前项目信息 -->
<groupId>com.codekitty</groupId>
<artifactId>springboot-study</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-study</name>
<description>study project for Spring Boot</description>
<!-- java版本 -->
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- web场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springboot单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- 剔除依赖 -->
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<!-- 打包插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2. 编写一个http接口
1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到
2、在包中新建一个HelloController类
路径:src/main/java/com/demo1/controller/HelloController.java
代码:
package com.demo1.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/hello")
@ResponseBody//添加当前注解,说明return的字符串只返回当前字符串,不指定返回web页面
public String hello() {
return "hello";
}
}
3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!
访问:http://localhost:8080/hello
4. 更改端口号
路径:src/main/resources/application.properties
代码:
# 更改项目的端口号
server.port=8081
运行结果:
访问:http://localhost:8081/hello/hello
5. 启动Banner修改
- 新建
src/main/resources/banner.txt
banner.txt文件 - 百度搜
spring boot banner 在线生成
,进入地址https://www.bootschool.net/ascii-art - 把搜索的图片,复制到
src/main/resources/banner.txt
,重启
我选择的ASCCI艺术字图片如下:
I DIDN'T LISTEN TO MY
MOTHER ABOUT CROSSING
MY EYES SO THEY STUCK
THIS WAY
HOLY GOD I NEED TOOL
\ .::::::`.::::::::::..
\ .''``````.:::::::::::::::::
\ .:',ere$ze c :::::::::::::::::::
\ ,'` e$$$$$$$-K eeeu...`````:::::::::
.zd>^leeu^R$%:FJ$$$$$$$$$$e.. ``::::::
.ed$$$$4$$$$$P-u@" ""?????R$$$$$$$hc. ``:::
.e$$F"..: P??7loF .:::::::::::.."""?R$$$e. `:.
zF".:::::::`"""'.:::::::::::::::::::::.`"?$$e.`
.::::::::::::':::::::::::::::::::::::::::::.`"=.
.:::::::::::::` `:::::::::::::::::::::::::::::::..
.:::::::::::::` ud$ec. ``:::::::::::::::::::::::::::::.
.:::::::::::`` .zd$$$$$$$ec.. ```::::::::::::::::::::::::::
.:::::::::::` "??$$$$$$$$$$$P ``::::::::::::::::::::::
::::::::::` .. $$*. ^$$$$$$$$$$ .e$**"" =e=.. ``::::::::::::::::.
::::::::: :::. . ^ $$P$$$$$$$$$$F .'$$$N4$$L'::. `:::::::::::::::
`::::::` :::::: $ '$$4$$$$$$$$% - : $$$F$$$$u`::::::. `:::::::::::.
:::::: :::::: .^m.-.e.$'$$$$$$$P. -)z$?Cd$$$$$u `:::::::. `:::::::::
`::::::::::: J$buFh52d4$$$$$$$LcccccCz$$$$$$$$": `::::::::..:::::::::
`::::::::: $$$$$$$$PJ$$$$$$$$$$$$$$$$$$$$$F.d$$. `::::::::::::::::`
`:::::: ?$$$$$$$F$$$$$$$$$$$$$$$$$$$$P x$$$$$$L `::::::::::::::
``:: dN ?$$$$$$N2$$?3$$$$$$$$$$$$$$P dP\$$$$$$$u `::::::::::::
`:'" $.`R$$P???$$P??????"$$$$$$$$ 9".d$$$$$$$$b.`::::::::::
:: R$$. ?$$r `..;; . $$$$$$$F"'$'$J?$$$$$$$f ::::::::
`:.^""""""."?$`niiiodfu$$$$$F"z$$ ^""~""""`..:::: ::::::
`::::::::::`?beCCbe$$$$""cd$$$$i .`::::::::::::. `:::`
``:::::::::`?$$$P"',cd$$$$$$$$r?k :::::::::::`.:::`
```::::::::: 4$$$$$$$$$$$$$"d$ ``::::::::::::`
``::: . $$$$$$$$$$$$fJ$F4$e.``:::::::`
dR `$$$$$$$$$$"x$$ d$$$$eu,.```
.e$ $E b?$$$$$$$".d$$ d$$$$$$$$$$$e..
..ee$$$$$ $$k`$$$$$$".d$$".$$$$$$$$$$$$$$$$$$hec.
.ze$$$$$$$$$$$b"$$heeeeeud$R" e$$$$$$$$$$$$$$$$$$$$$$$$$e.
z$$$$$$$$$$$$$$$$$$h`?c""""J$R z$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
.ee$$$$$$$$$$$$$$$$$$$$$$hc"xJ>=".zd$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
.d$$$$$$$$$$$$$$$$$$$$$$$$$$$$he$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
- 重启结果
三、 原理初探
1. pom.xml
1. 父依赖
其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
点击spring-boot-starter-parent/org.springframework.boot
进去,发现还有一个父依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.0</version>
</parent>
-
这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
-
我们在写或者引入springboot依赖的时候,不需要指定版本,因为有这些版本仓库;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;
2. 启动器 spring-boot-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
-
springboot-boot-starter-xxx:就是spring-boot的场景启动器
-
spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;
-
SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;
2. 主程序
路径:src/main/java/com/demo1/Demo1Application.java
代码:
package com.demo1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication 来标注一个主程序类
//标注这个类是一个springboot的应用
@SpringBootApplication
public class Demo1Application {
//以为是启动了一个方法,没想到启动了一个服务
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
}
但是一个简单的启动类并不简单!我们来分析一下这些注解都干了什么
1. @SpringBootApplication
作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
- 点击@SpringBootApplication进入
- 进入SpringBootApplication.class
注解说明:
1. @ComponentScan:
- 这个注解在Spring中很重要 ,它对应XML配置中的元素。
- 作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
2. @SpringBootConfiguration:SpringBoot的配置类
- 作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
- 我们继续点击进去
@SpringBootConfiguration
这个注解查看
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Indexed;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
- 这里的
@Configuration
,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
1.2. 我们继续点击进去@Configuration
这个注解查看
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
- 里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!
3. @EnableAutoConfiguration:开启自动配置功能
@EnableAutoConfiguration :开启自动配置功能
- 以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;
- @EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
1. 点进@EnableAutoConfiguration:开启自动配置功能注解接续查看:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
- 去掉
@Target({ElementType.TYPE})、@Retention(RetentionPolicy.RUNTIME)、@Documented、@Inherited
四个元注解外
1.2 @AutoConfigurationPackage: 自动配置包。点进入
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
- @Import({Registrar.class}):Spring底层注解@import , 给容器中导入一个组件
- Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;
1.3 @Import({AutoConfigurationImportSelector.class})::给容器导入组件 ;
AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:
- 点击
AutoConfigurationImportSelector.class
自动配置导入选择器进入
- 这个类中有一个这样的方法
// 获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//这里的getSpringFactoriesLoaderFactoryClass()方法
//返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
如下图所展示:
- 这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法
- 点击getCandidateConfigurations方法里面的loadFactoryNames方法,如下图
- loadFactoryNames方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
- 我们继续点击查看 loadSpringFactories 方法
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
- 发现一个多次出现的文件:spring.factories,全局搜索它
- spring.factories
- 我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
- META-INF/spring.factories:自动配置的核心文件,如下图:
- WebMvcAutoConfiguration
我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration
-
点击WebMvcAutoConfiguration进入这个类如下图:
-
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
白话说:
- Springboot所有自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的strater,就有了对应的启动器,有了启动器,我们自动装配就会生效,然后配置成功
4. 结论:
-
SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
-
将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
-
整个javaEE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
-
它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
-
有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
2. SpringApplication
不简单的方法
我最初以为就是运行了一个main方法,没想到却开启了一个服务;
package com.demo1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication 来标注一个主程序类
//标注这个类是一个springboot的应用
@SpringBootApplication
public class Demo1Application {
//以为是启动了一个方法,没想到启动了一个服务
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
}
SpringApplication.run分析
- 分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;
1. SpringApplication的实例化
这个类主要做了以下四件事情:
-
1、推断应用的类型是普通的项目还是Web项目
-
2、查找并加载所有可用初始化器 , 设置到initializers属性中
-
3、找出所有的应用程序监听器,设置到listeners属性中
-
4、推断并设置main方法的定义类,找到运行的主类
2. run
四、Springboot配置文件
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
路径: src/main/resources/application.yaml
application.properties
- 语法结构 :key=value
server.port=8081
application.yml
- 语法结构 :key:空格 value
server:
port: 8081
配置文件的作用 :修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!
server.port=8081
1. yaml概述
YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
这种语言以数据作为中心,而不是以标记语言为重点!
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
传统xml配置:
<server>
<port>8081<port>
</server>
yaml配置:
server:
prot: 8080
2.yaml基础语法
说明:语法要求严格!
-
1、空格不能省略
-
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
-
3、属性和值的大小写都是十分敏感的。
字面量:普通的值 [ 数字,布尔值,字符串 ]
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
k: v
注意:
- “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: “kuang \n shen” 输出 :kuang 换行 shen
- ‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen
1. 对象、Map(键值对)
#对象、Map格式
k:
v1:
v2:
在下一行来写对象的属性和值得关系,注意缩进;比如:
student:
name: qinjiang
age: 3
行内写法
student: {name: qinjiang,age: 3}
2. 数组( List、set )
用 - 值表示数组中的一个元素,比如:
pets:
- cat
- dog
- pig
行内写法
pets: [cat,dog,pig]
3. 修改SpringBoot的默认端口号
配置文件中添加,端口号的参数,就可以切换端口;
server:
port: 8082
3. 注入配置文件
给属性复制的多种方式:
- @value : 一个个指定
- yarm配置文件 :批量注入配置中的文件,yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!
- 加载指定的配置文件:一个个指定
在springboot项目中的resources目录下新建一个文件application.yml
1. @Value bean注入属性值
1、编写一个实体类 Dog;
给bean注入属性值@Value
路径:src/main/java/com/demo1/pojo/Dog.java
代码:
package com.demo1.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component//组件
public class Dog {
@Value("旺财")
private String name;
@Value("3")
private Integer age;
public Dog() {
}
public Dog(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2 、 测试
路径:src/test/java/com/demo1/Demo1ApplicationTests.java
代码:
package com.demo1;
import com.demo1.pojo.Dog;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Demo1ApplicationTests {
@Resource
private Dog dog;
@Test
void contextLoads() {
System.out.println(dog);
}
}
测试结果:
2. yaml配置的方式进行注入
1、 新建实体类Person
路径:src/main/java/com/demo1/pojo/Person.java
代码:
package com.demo1.pojo;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String, Object> maps;
private List<Object> lists;
private Dog dog;
public Person() {
}
public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
this.name = name;
this.age = age;
this.happy = happy;
this.birth = birth;
this.maps = maps;
this.lists = lists;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getHappy() {
return happy;
}
public void setHappy(Boolean happy) {
this.happy = happy;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public List<Object> getLists() {
return lists;
}
public void setLists(List<Object> lists) {
this.lists = lists;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", happy=" + happy +
", birth=" + birth +
", maps=" + maps +
", lists=" + lists +
", dog=" + dog +
'}';
}
}
运行结果:
Dog{name='旺财', age=3}
2、yaml配置
路径:src/main/resources/application.yaml
代码
person:
name: fj
age: 3
happy: false
birth: 2019/11/02
maps: {k1: v1,k2: v2}
lists:
- code
- music
- girl
dog:
name: 往往
age: 3
3、Person实体类引入
路径:src/main/java/com/demo1/pojo/Person.java
引入报错:
-
当前爆红不影响程序运行
-
IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!
-
依赖代码,当前代码放到
pom
的dependencies
里面,刷新即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
代码:
package com.demo1.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String, Object> maps;
private List<Object> lists;
private Dog dog;
public Person() {
}
public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
this.name = name;
this.age = age;
this.happy = happy;
this.birth = birth;
this.maps = maps;
this.lists = lists;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getHappy() {
return happy;
}
public void setHappy(Boolean happy) {
this.happy = happy;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public List<Object> getLists() {
return lists;
}
public void setLists(List<Object> lists) {
this.lists = lists;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", happy=" + happy +
", birth=" + birth +
", maps=" + maps +
", lists=" + lists +
", dog=" + dog +
'}';
}
}
4 、测试
路径:src/test/java/com/demo1/Demo1ApplicationTests.java
代码:
package com.demo1;
import com.demo1.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Demo1ApplicationTests {
@Resource
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
运行结果:
3. 加载指定的配置文件
1、创建文件
路径:src/main/resources/fj.properties
代码:
name=fj
2、实体类引入
路径:src/main/java/com/demo1/pojo/Person.java
代码:
package com.demo1.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册bean
//@ConfigurationProperties(prefix = "person")
//1. 加载指定的配置文件
@PropertySource(value = "classpath:fj.properties")
public class Person {
//2. 表达式取出配置文件
@Value("${name}")
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String, Object> maps;
private List<Object> lists;
private Dog dog;
public Person() {
}
public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
this.name = name;
this.age = age;
this.happy = happy;
this.birth = birth;
this.maps = maps;
this.lists = lists;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getHappy() {
return happy;
}
public void setHappy(Boolean happy) {
this.happy = happy;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public List<Object> getLists() {
return lists;
}
public void setLists(List<Object> lists) {
this.lists = lists;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", happy=" + happy +
", birth=" + birth +
", maps=" + maps +
", lists=" + lists +
", dog=" + dog +
'}';
}
}
3、测试
package com.demo1;
import com.demo1.pojo.Dog;
import com.demo1.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
class Demo1ApplicationTests {
@Resource
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
运行结果:
4. @Autowired报错
关于这类组件注解
- 关键点来了下面这四点实际上效果是一样的都是注入到容器中,不过在不同的包名内最好按规范来写:
- 这四个注解的功能都是一样的,都是代表某个类注册到Spring中,装配到Bean中
@Repository:代表dao层
@Component:这个惯用实体层
@Service : 这个用于service层
@Controller : 这个用于Controller
@Resource与@Autowired的区别?
-
@Autowired按byType自动注入。
-
@Resource默认按byName自动注入。
-
但是@Resource有两个属性是比较重要的,分是name和type;
-
如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略;
-
-
@Autowired是spring自己定义的注解
-
@Resource是J2EE的,由JSR-250规范定义。
-
PS:个人建议用@Resource,不为什么,减少与spring的耦合。
5. 配置文件占位符
路径:src/main/resources/application.yaml
代码:
person:
name: fj${random.uuid} #随机uuid
age: ${random.int} #随机int
happy: false
birth: 2019/11/02
maps: { k1: v1,k2: v2 }
lists:
- code
- music
- girl
dog:
name: ${person.hello:hello}_旺财 #如果有就取person.hello的值,否则取值hello
age: 3
- hello无值测试:
- 配置文件添加hello值
person:
name: fj${random.uuid} #随机uuid
age: ${random.int} #随机int
happy: false
birth: 2019/11/02
maps: { k1: v1,k2: v2 }
hello: ffff
lists:
- code
- music
- girl
dog:
name: ${person.hello:hello}_旺财 #如果有就取person.hello的值,否则取值hello
age: 3
hello有值测试:
4. yaml功能
yarm配置文件和@Value功能对比图:
- @ConfigurationProperties只需要写一次即可,@value则需要每个字段都添加
- 松散绑定:这个什么意思呢?比如我的yaml中写的是last-name,这个和lastName是一样的,-后面跟着的字母默认是大写的。这就是松散绑定
- JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证,可以保证数据的合法性
- 复杂类型封装,yaml中可以封装对象,使用@value就不支持
结论:
- 配置yml和配置properties都可以获取到值,强烈推荐yml
- 如果我们某个业务中,只需要获取配置文件中的某个值,可以使用一下@value
- 如果说,我们专门编写了一个JavaBean来和配置文件进行映射,就直接使用@configurationProperties,不要犹豫!
1. 松散绑定
1. 实体类
路径:src/main/java/com/demo1/pojo/Dog.java
代码:
package com.demo1.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component//组件
@ConfigurationProperties(prefix = "dog")
public class Dog {
private String firstName;
private Integer age;
public Dog() {
}
public Dog(String firstName, Integer age) {
this.firstName = firstName;
this.age = age;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"firstName='" + firstName + '\'' +
", age=" + age +
'}';
}
}
2. yarm配置文件
路径:src/main/resources/application.yaml
代码:
dog:
first_name: 阿黄 #first-name和first_name都可以
age: 3
3. 测试
路径:src/test/java/com/demo1/Demo1ApplicationTests.java
代码:
package com.demo1;
import com.demo1.pojo.Dog;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
class Demo1ApplicationTests {
@Resource
private Dog dog;
@Test
void contextLoads() {
System.out.println(dog);
}
}
结果:松散绑定使用的是set注入
2. JSR303校验规则
1. pom引入依赖
引入后,刷新
路径:pom.xml
代码:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.6.1</version>
</dependency>
2. 实体类引入校验
- Springboot中可以用
@validated
来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。 - 我们这里来写个注解让我们的
name
只能支持Email
格式;
路径:src/main/java/com/demo1/pojo/Person.java
代码:
package com.demo1.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Email;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated//数据校验
public class Person {
//测试校验email
@Email(message = "用户名不合法")
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String, Object> maps;
private List<Object> lists;
private Dog dog;
public Person() {
}
public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
this.name = name;
this.age = age;
this.happy = happy;
this.birth = birth;
this.maps = maps;
this.lists = lists;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getHappy() {
return happy;
}
public void setHappy(Boolean happy) {
this.happy = happy;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public List<Object> getLists() {
return lists;
}
public void setLists(List<Object> lists) {
this.lists = lists;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", happy=" + happy +
", birth=" + birth +
", maps=" + maps +
", lists=" + lists +
", dog=" + dog +
'}';
}
}
3. 测试
路径:src/test/java/com/demo1/Demo1ApplicationTests.java
代码:
package com.demo1;
import com.demo1.pojo.Dog;
import com.demo1.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
class Demo1ApplicationTests {
@Resource
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
4. 校验参数说明
校验参数说明:
使用说明:
@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 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
源码所在位置:
3. 多环境切换
1. 配置文件加载位置
外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!
- 配置路径位置:
- 配置文件优先级
- springboot 启动会扫描以下位置的
application.properties
或者application.yml
文件作为Spring boot的默认配置文件:
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
-
文件位置几优先级说明图片演示:
-
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
-
SpringBoot会从这四个位置全部加载主配置文件;互补配置;
-
我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;
2. profiles多环境配置
- 新建测试环境配置文件
路径:src/main/resources/application-test.properties
代码:
server.port=8081
- 新建开发环境配置文件
路径:src/main/resources/application-dev.properties
代码:
server.port=8082
- 新建默认环境配置文件
路径:src/main/resources/application.properties
代码:
server.port=8080
#springboot多环境配置:可以选择激活哪一个配置文件
spring.profiles.active=dev
3. yaml多环境配置
路径:src/main/resources/application.yml
代码:
# 默认环境
server:
port: 8081
# 绑定当前文件为测试环境test
spring:
profiles:
active: test
---
# 开发环境
server:
port: 8082
spring:
config:
activate:
on-profile: dev
---
# 测试环境
server:
port: 8083
spring:
config:
activate:
on-profile: test
五、自动配置原理
配置文件到底能写什么?怎么写?
SpringBoot官方文档中有大量的配置,我们无法全部记住
1. 分析自动配置原理
1. 了解原理
我们以HttpEncodingAutoConfiguration
(Http编码自动配置)为例解释自动配置原理;
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//servlet包
package org.springframework.boot.autoconfigure.web.servlet;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.server.Encoding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration(proxyBeanMethods = false)
//启动指定类的ConfigurationProperties功能;
//进入这个ServerProperties查看,将配置文件中对应的值和ServerProperties绑定起来;
//并把ServerProperties加入到ioc容器中
@EnableConfigurationProperties(ServerProperties.class)
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
-
一但这个配置类生效;这个配置类就会给容器中添加各种组件;
-
这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
-
所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
-
配置文件能配置什么就可以参照某个功能对应的这个属性类
点进@EnableConfigurationProperties(ServerProperties.class)
中的ServerProperties.class
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
/**
* Network address to which the server should bind.
*/
private InetAddress address;
我们去配置文件里面试试前缀,看提示!
这就是自动装配的原理!
2. 精髓
-
1、SpringBoot启动会加载大量的自动配置类
-
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
-
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
-
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
-
xxxxAutoConfigurartion
:自动配置类;给容器中添加组件 -
xxxxProperties
:封装配置文件中相关属性;
3. 了解:@Conditional
1. 了解参数
-
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
-
@Conditional
派生注解(Spring注解版原生的@Conditional作用) -
作用:必须是
@Conditional
指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
- 那么多的自动配置类,必须在一定的条件下才能生效;
- 也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
2. 我们怎么知道哪些自动配置类生效?
-
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
-
路径:
src/main/resources/application.yml
代码:
# 在我们这配置文件中能配置的东西,都存在一个固有规律
# xxxAutoConfiguration:默认值 xxxProperties 和配置文件绑定,我们就可以使用自定义的配置了
debug: true
- 运行结果:
参数说明:
Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)
六、SpringBoot Web开发
SpringBoot到底帮我们配置了什么,我们能不能进行修改?能修改哪些东西?能不能扩展?
xxxxAutoConfiguration…向容器中自动配置组件
xxxxProperties:自动配置类,装配配置文件中自定义的一些内容!
要解决的问题:
- 导入静态资源,…
- 首页
- jsp,模板引擎Thymeleaf
- 装配扩展SpringMVC
- 增删改查
- 拦截器
- 国际化!
1. 静态资源
我们先来聊聊这个静态资源映射规则:
- 新建项目springboot-03-web
- 写一个controller,运行查看项目是否运行成功
SpringBoot中,SpringMVC的web配置都在WebMvcAutoConfiguration
这个配置类里面;
1. 搜索WebMvcAutoConfigurationAdapter
双击shift弹出搜索窗口
2. 查看WebMvcAutoConfigurationAdapter
配置方法;
3. 静态资源规则
1. 导入静态资源
- 查看
addResourceHandlers
添加资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
// 已禁用默认资源处理
logger.debug("Default resource handling disabled");
return;
}
// 添加资源处理:注册webjars 配置
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
//添加资源处理:加载本地静态资源《自定义静态资源》
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
读一下源代码:比如所有的 /webjars/**
, 都需要去 classpath:/META-INF/resources/webjars/
找对应的资源;
1.1. 什么是webjars 呢?
-
Webjars
本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。 -
使用
SpringBoot
需要使用Webjars
,我们可以去搜索一下:
网站:https://www.webjars.org -
要使用jQuery,我们只要要引入jQuery对应版本的pom依赖刷新即可!
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
- 导入完毕,查看webjars目录结构,并访问Jquery.js文件!
只要是静态资源,SpringBoot就会去对应的路径寻找资源,
我们这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js
注意:导入依赖后要重启开启服务才能加载,访问才能成功
2. 自定义静态资源
那我们项目中要是使用自己的静态资源该怎么导入呢?我们看下一行代码;
发现第二种添加映射规则 : this.resourceProperties.getStaticLocations()
,我们可以点进去看一下分析:
Resource 可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即下面数组的内容。
所以得出结论,以下四个目录存放的静态资源可以被我们识别:
"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;
比如我们访问http://localhost:8080/1.js
, 他就会去这些文件夹中寻找对应的静态资源文件;
优先级:resource->static->public
resource:放上传资源,upload上传文件等
static:静态资源,图片
public:公共的资源,js等
3. 自定义静态资源路径
我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置;
spring.resources.static-locations=classpath:/coding/,classpath:/kuang/
一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!
2. 首页和图标定制
1. 查看源码
双击shifit,搜索WebMvcAutoConfiguration.java
,进入WebMvcAutoConfiguration.java
,查看WelcomePageHandlerMapping
《欢迎页处理映射》接口,查看加载的资源为静态资源
点击this.mvcProperties.getStaticPathPattern()
查看,发现/**
的所有资源文件被加载
点击this.webProperties.getLocale()
发现本地资源被加载
2. 首页index文件
1. 新建index.html
路径:src/main/resources/public/index.html
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello</title>
</head>
<body>
首页
</body>
</html>
2. 测试
访问:http://localhost:8080/
运行:
3. 图标
- 上传、复制名为
favicon.ico
的图片放到静态资源文件夹/resources
或者/public
或者/static
下
2. 重启项目,并且清除浏览器缓存,访问图标修改
3. Thymeleaf模板引擎
1. 介绍说明
前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。
jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的
。
那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?
SpringBoot推荐你可以来使用模板引擎:
模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:
模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。
我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。
2. 引入Thymeleaf
怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。
给大家三个网址:
-
Thymeleaf 官网:https://www.thymeleaf.org/
-
Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf
-
Spring官方文档:点击进入页面,搜索Thymeleaf,进入pom,找到依赖,复制到项目的pom,刷新
-
找到对应的pom依赖:可以适当点进源码看下本来的包!
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
- Maven会自动下载jar包,我们可以去看下下载的东西;
3. Thymeleaf分析
前面呢,我们已经引入了Thymeleaf,那这个要怎么使用呢?
我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。
我们去找一下Thymeleaf的自动配置类:ThymeleafProperties
我们可以在其中看到默认的前缀和后缀!
我们只需要把我们的html
页面放在类路径下的templates
下,thymeleaf
就可以帮我们自动渲染了。
使用thymeleaf
什么都不需要配置,只需要将他放在指定的文件夹下即可!
4. 模板测试
1. 新建html页面
路径:src/main/resources/templates/index.html
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello</title>
</head>
<body>
index
</body>
</html>
2. 新建controller页面
路径:src/main/java/com/xxx/controller/IndexController.java
代码:
package com.xxx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
//在templates 目录下面的说有页面,只能通过controller跳转
//需要模板引擎的支持 thymeleaf
@Controller
public class IndexController {
@RequestMapping("/index")
public String test() {
return "index";
}
}
3. 运行
访问:http://localhost:8080/index
5. Thymeleaf 语法学习
要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;
- Thymeleaf 官网:https://www.thymeleaf.org/
- Thymeleaf 官网文档下载、文档在线浏览选择地址:https://www.thymeleaf.org/documentation.html
- Thymeleaf 官网在线浏览文档地址:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#using-texts
1. 数据传输
1. controller传值
路径:src/main/java/com/xxx/controller/IndexController.java
代码:
package com.xxx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
//在templates 目录下面的说有页面,只能通过controller跳转
//需要模板引擎的支持 thymeleaf
@Controller
public class IndexController {
@RequestMapping("/index")
public String test(Model model) {
model.addAttribute("msg", "hello ,springboot");
return "index";
}
}
2. 导入约束
<!--引入约束 xmlns:th="http://www.thymeleaf.org -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
3. html接收
路径:src/main/resources/templates/index.html
代码:
<!DOCTYPE html>
<!--引入约束-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>hello</title>
</head>
<body>
<!--所有的html元素都可以被thymeleaf 替换结果 th:元素名-->
<h1 th:text="${msg}"></h1>
</body>
</html>
4. 测试
访问:http://localhost:8080/index
2. 转义和不转义
1. controller传值
路径:src/main/java/com/xxx/controller/IndexController.java
代码:
package com.xxx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
//在templates 目录下面的说有页面,只能通过controller跳转
//需要模板引擎的支持 thymeleaf
@Controller
public class IndexController {
@RequestMapping("/index")
public String test(Model model) {
model.addAttribute("msg", "<h1>hello ,springboot</h1>");
return "index";
}
}
2. html接收
路径:src/main/resources/templates/index.html
代码:
<!DOCTYPE html>
<!--引入约束-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>hello</title>
</head>
<body>
<!--所有的html元素都可以被thymeleaf 替换结果 th:元素名-->
<!--不转义 <h1>hello ,springboot</h1>-->
<dev th:text="${msg}"></dev>
<!--转义 hello ,springboot-->
<dev th:utext="${msg}"></dev>
</body>
</html>
3. 测试
访问:http://localhost:8080/index
3. 遍历
1. controller传值
路径:src/main/java/com/xxx/controller/IndexController.java
代码:
package com.xxx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Arrays;
//在templates 目录下面的说有页面,只能通过controller跳转
//需要模板引擎的支持 thymeleaf
@Controller
public class IndexController {
@RequestMapping("/index")
public String test(Model model) {
model.addAttribute("msg", "<h1>hello ,springboot</h1>");
model.addAttribute("users", Arrays.asList("php", "java"));
return "index";
}
}
2. html接收
路径:src/main/resources/templates/index.html
代码:
<!DOCTYPE html>
<!--引入约束-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>hello</title>
</head>
<body>
<!--所有的html元素都可以被thymeleaf 替换结果 th:元素名-->
<!--不转义 <h1>hello ,springboot</h1>-->
<dev th:text="${msg}"></dev>
<!--转义 hello ,springboot-->
<dev th:utext="${msg}"></dev>
<hr>
<!--建议使用这个-->
<h3 th:each="user:${users}" th:text="${user}"></h3>
<hr>
<h3 th:each="user:${users}">[[${user}]]</h3>
</body>
</html>
3. 测试
访问:http://localhost:8080/index
6. Thymeleaf 总结
OK,入门搞定,我们来认真研习一下Thymeleaf的使用语法!
1、我们可以使用任意的 th:attr 来替换Html中原生属性的值!
2、我们能写哪些表达式呢?
Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
1)、获取对象的属性、调用方法
2)、使用内置的基本对象:#18
#ctx : the context object.
#vars: the context variables.
#locale : the context locale.
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.
3)、内置的一些工具对象:
#execInfo : information about the template being processed.
#uris : methods for escaping parts of URLs/URIs
#conversions : methods for executing the configured conversion service (if any).
#dates : methods for java.util.Date objects: formatting, component extraction, etc.
#calendars : analogous to #dates , but for java.util.Calendar objects.
#numbers : methods for formatting numeric objects.
#strings : methods for String objects: contains, startsWith, prepending/appending, etc.
#objects : methods for objects in general.
#bools : methods for boolean evaluation.
#arrays : methods for arrays.
#lists : methods for lists.
#sets : methods for sets.
#maps : methods for maps.
#aggregates : methods for creating aggregates on arrays or collections.
==================================================================================
Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
Message Expressions: #{...}:获取国际化内容
Link URL Expressions: @{...}:定义URL;
Fragment Expressions: ~{...}:片段引用表达式
Literals(字面量)
Text literals: 'one text' , 'Another one!' ,…
Number literals: 0 , 34 , 3.0 , 12.3 ,…
Boolean literals: true , false
Null literal: null
Literal tokens: one , sometext , main ,…
Text operations:(文本操作)
String concatenation: +
Literal substitutions: |The name is ${name}|
Arithmetic operations:(数学运算)
Binary operators: + , - , * , / , %
Minus sign (unary operator): -
Boolean operations:(布尔运算)
Binary operators: and , or
Boolean negation (unary operator): ! , not
Comparisons and equality:(比较运算)
Comparators: > , < , >= , <= ( gt , lt , ge , le )
Equality operators: == , != ( eq , ne )
Conditional operators:条件运算(三元运算符)
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special tokens:
No-Operation: _
4. MVC自动配置原理
1. 介绍说明
在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。
只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!
地址 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration
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.
我们来仔细对照,看一下它怎么实现的,它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?
2. ContentNegotiatingViewResolver 内容协商视图解析器
自动配置了ViewResolver
,就是我们之前学习的SpringMVC的视图解析器;
即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
我们去看看这里的源码:我们找到 WebMvcAutoConfiguration
, 然后搜索ContentNegotiatingViewResolver
。找到如下方法!
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
//内容协商解决
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
我们可以点进ContentNegotiatingViewResolver
这类看看!找到对应的解析视图的代码;
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图对象
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择一个最适合的视图对象,然后把这个对象返回
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
// .....
}
我们继续点进去看,他是怎么获得候选的视图的呢?
getCandidateViews
中看到他是把所有的视图解析器拿来,进行while循环,挨个解析!
Iterator var5 = this.viewResolvers.iterator();
所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的!
protected void initServletContext(ServletContext servletContext) {
Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
ViewResolver viewResolver;
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList(matchingBeans.size());
Iterator var3 = matchingBeans.iterator();
while(var3.hasNext()) {
viewResolver = (ViewResolver)var3.next();
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
} else {
for(int i = 0; i < this.viewResolvers.size(); ++i) {
viewResolver = (ViewResolver)this.viewResolvers.get(i);
if (!matchingBeans.contains(viewResolver)) {
String name = viewResolver.getClass().getName() + i;
this.obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolver, name);
}
}
}
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?
我们可以自己给容器中去添加一个视图解析器;这个类就会帮我们自动的将它组合进来;我们去实现一下
1. 新建视图解析器
路径:src/main/java/com/xxx/config/MyMvcConfig.java
代码:
package com.xxx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
//全面扩展springmvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//viewResolvers 实现了视图解析器接口的类,我们就可以把它看做视图解析器
@Bean //放到bean中
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//我们写一个静态内部类,视图解析器就需要实现ViewResolver接口
private static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
2. 添加断电
怎么看我们自己写的视图解析器有没有起作用呢?
我们给 DispatcherServlet.class
中的 doDispatch
方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中
3. Debug运行
4. 查看Debug信息
1. 访问浏览器
运行成功,浏览器输入http://localhost:8080/
点击Enter
确定进入页面
进入页面之前因为有debug模式,一直转
2. 查看控制台信息
- 找到this
- 找到视图解析器,我们看到我们自己定义的就在这里了;
所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了!
5.转换器和格式化器
1. 找到格式化转换器:
搜索WebMvcAutoConfiguration.java
进入,然后查找FormattingConversionService
方法
@Bean
@Override
public FormattingConversionService mvcConversionService() {
Format format = this.mvcProperties.getFormat();
WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
.dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
addFormatters(conversionService);
return conversionService;
}
点击getFormat()
进入WebMvcProperties.java
点击this.format
,可以看到在我们的Properties
文件中,我们可以进行自动配置它!
private final Format format = new Format();
点击new Format()
public static class Format {
/**
* Date format to use, for example 'dd/MM/yyyy'.
*/
private String date;
/**
* Time format to use, for example 'HH:mm:ss'.
*/
private String time;
/**
* Date-time format to use, for example 'yyyy-MM-dd HH:mm:ss'.
*/
private String dateTime;
public String getDate() {
return this.date;
}
public void setDate(String date) {
this.date = date;
}
public String getTime() {
return this.time;
}
public void setTime(String time) {
this.time = time;
}
public String getDateTime() {
return this.dateTime;
}
public void setDateTime(String dateTime) {
this.dateTime = dateTime;
}
}
如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:
6. 修改SpringBoot的默认配置
这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。
SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!
扩展使用SpringMVC 官方文档如下:
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 Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己的@configuration类,该类的类型为WebMVCConfiguer,但不包含@EnableWebMvc。如果希望提供RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义实例,可以声明WebMVCreRegistrationAdapter实例以提供此类组件。
我们要做的就是编写一个@Configuration
注解类,并且类型要为WebMvcConfigurer
,还不能标注@EnableWebMvc
注解;我们去自己写一个;我们新建一个包叫config
,写一个类MyMvcConfig
;
1. 新建controller
路径:src/main/java/com/xxx/config/MyMvcConfig.java
代码:
package com.xxx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
//全面扩展springmvc,如果我们要扩展springmvc,官方建议我们这样做
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/fj").setViewName("index");
}
}
2. 测试
访问:http://localhost:8080/fj
确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!
3. 原理
1、WebMvcAutoConfiguration
是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
<WebMvc自动适配器>
2、这个类上有一个注解,在做其他自动配置时会导入<启用WebMVC配置>:@Import(EnableWebMvcConfiguration.class)
3、我们点进EnableWebMvcConfiguration
这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration
4、我们点进父类DelegatingWebMvcConfiguration
这个类看一下,这个父类中有这样一段代码
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
public DelegatingWebMvcConfiguration() {
}
// 从容器中获取所有的webmvcConfigurer
@Autowired(
required = false
)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
5、我们可以在这个类中去寻找一个我们刚才设置的viewController
当做参考,发现它调用了一个
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
6、我们点进去看一下
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自己的配置类,我们自己的配置类当然也会被调用;
4. 全面接管SpringMVC
官方文档:
If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.
-
全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!
-
只需在我们的配置类中要加一个
@EnableWebMvc
。 -
我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;
1. 测试
不加注解之前,访问首页:
路径:src/main/java/com/xxx/config/MyMvcConfig.java
代码:
package com.xxx.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
//全面扩展springmvc,如果我们要扩展springmvc,官方建议我们这样做
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/fj").setViewName("index");
}
}
运行:
给配置类加上注解:@EnableWebMvc
路径:src/main/java/com/xxx/config/MyMvcConfig.java
代码:
package com.xxx.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
//全面扩展springmvc,如果我们要扩展springmvc,官方建议我们这样做
@Configuration
@EnableWebMvc //点进去这个注解发现导入了一个类:DelegatingWebMvcConfiguration
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/fj").setViewName("index");
}
}
访问:
我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;
当然,我们开发中,不推荐使用全面接管SpringMVC
2. 原理
思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:
1、点击去@EnableWebMvc
注解,这里发现它是导入了一个类,我们可以继续进去看
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
2、点击进去{DelegatingWebMvcConfiguration.class
,它继承了一个父类 WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// ......
}
3、我们来回顾一下Webmvc
自动配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
总结一句话:@EnableWebMvc
将WebMvcConfigurationSupport
组件导入进来了;
而导入的WebMvcConfigurationSupport
只是SpringMVC最基本的功能!
在SpringBoot中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意~
七、员工系统管理项目
1. 准备工作
1. 模板下载
多种模板下载:https://sc.chinaz.com/tag_moban/bootstrap.html
当前模板地址:https://sc.chinaz.com/moban/211020582620.htm
bootstrap模板下载:https://getbootstrap.com/docs/4.0/examples/
项目数据库等资源搭建路径:https://www.cnblogs.com/coopermini/p/14974667.html
当前项目源码路径:https://github.com/fj111111/employee.git
2. 导入静态资源
新建Spring Initializr 项目
把资源导入项目,html页面把自己需要的页面导入template,static全部样式导入static
3. 导入pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xxx</groupId>
<artifactId>springboot-03-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-03-web</name>
<description>springboot-03-web</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--Thymeleaf-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4. 创建实体类
1. 创建部门实体类
路径:src/main/java/com/xxx/pojo/Department.java
代码:
package com.xxx.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
2. 创建员工实体类
路径:src/main/java/com/xxx/pojo/Employee.java
代码:
package com.xxx.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
//员工表
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private Integer id;
private String name;
private String email;
private Integer gender; //0 女 1男
private Department department;
private Date date;
}
5. 创建dao
1. 创建部门Dao
路径:src/main/java/com/xxx/dao/DepartmentDao.java
代码:
package com.xxx.dao;
import com.xxx.pojo.Department;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
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();
}
//通过id获得部门
public Department getDepartmentById(Integer id) {
return departments.get(id);
}
}
2. 创建员工Dao
路径:src/main/java/com/xxx/dao/EmployeeDao.java
代码:
package com.xxx.dao;
import com.xxx.pojo.Department;
import com.xxx.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class EmployeeDao {
//模拟员工数据
private static Map<Integer, Employee> employees = null;
@Autowired
private DepartmentDao departmentDao;
static {
employees = new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "AA", "A1251694436@qq.com", 0, new DepartmentDao().getDepartmentById(101)));
employees.put(1002, new Employee(1002, "BB", "B1251694445@qq.com", 0, new DepartmentDao().getDepartmentById(102)));
employees.put(1003, new Employee(1003, "CC", "C1251694478@qq.com", 0, new DepartmentDao().getDepartmentById(103)));
employees.put(1004, new Employee(1004, "DD", "D12516944752@qq.com", 0, new DepartmentDao().getDepartmentById(104)));
employees.put(1005, new Employee(1005, "EE", "E1251694498@qq.com", 0, new DepartmentDao().getDepartmentById(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> getAll() {
return employees.values();
}
//通过id查询
public Employee getEmployeeById(Integer id) {
return employees.get(id);
}
//删除
public void delete(Integer id) {
employees.remove(id);
}
}
6. 首页访问控制
1. controller
- 新建IndexController
路径:src/main/java/com/xxx/controller/IndexController.java
代码:
package com.xxx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
@RequestMapping({"/","/index.html"})
public String index(){
return "index";
}
}
2. springboot托管
- 新建IndexController
路径:src/main/java/com/xxx/config/MyMvcConfig.java
代码:
package com.xxx.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//全面扩展springmvc,如果我们要扩展springmvc,官方建议我们这样做
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("index.html").setViewName("index");
}
}
3. 测试
删除任何一种方式,运行代码,运行结果都是成功的
访问:http://127.0.0.1:8081/
7. thymeleaf静态资源
1. 国际化配置
1. 确保编辑器编码
2. 新建文件夹i18n
说明:i18n是internationalization
缩写,i开头,n结尾,中间有18位字符
路径:src/main/resources/i18n
3. 新建文件
- 可视化编辑工具
- 搜索
Resource Bundle Editor
安装插件,apply使用,可以可视化编辑配置文件
1. login.properties
默认配置
路径:src/main/resources/i18n/login.properties
代码:
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
2. login_en_US.properties
英文配置
路径:src/main/resources/i18n/login_en_US.properties
代码:
login.btn=Sign in
login.password=Password
login.remember=Remember me
login.tip=Please sign in
login.username=Username
3. login_zh_CN.properties
中文配置
路径:src/main/resources/i18n/login_zh_CN.properties
代码:
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
4. 配置
路径:src/main/resources/application.properties
代码:
spring.messages.basename=i18n.login
2. 更改页面
1. 页面更改路径
路径:src/main/resources/templates/index.html
代码:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<!--<p th:text="${msg}" style="color: red"></p>-->
<form class="form-signin" th:action="@{/user/login}" method="post">
<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 in</h1>
<!--<p th:text="${msg}" style="color: red" th:if="${not #strings.isEmpty(msg)}" ></p>-->
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" name="password" class="form-control" 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-pr
imary btn-block" type="submit" >[[#{login.btn}]]</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(language='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(language='en_US')}">English</a>
</form>
</body>
</html>
运行路径:http://localhost:8080/
2. 页面实现国际化中英文切换
1. 创建配置类
路径:src/main/java/com/xxx/config/MyLocaleResolver.java
代码:
package com.xxx.config;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
/**
* 国际化解析器
*/
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数
String language = request.getParameter("language");
Locale locale = Locale.getDefault();//如果没有就使用默认的
//如果这个值不为空
if (!StringUtils.isEmpty(language)) {
//zh_CN
String[] split = language.split("_");
//国家地区
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
2. 配置
路径:src/main/java/com/xxx/config/MyMvcConfig.java
代码:
package com.xxx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//全面扩展springmvc,如果我们要扩展springmvc,官方建议我们这样做
@Configuration
public class MyMvcConfig 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();
}
}
3. 测试
访问:http://localhost:8080/index.html?language=en_US
2. 登录
1. 更改登录页面
路径:src/main/resources/templates/index.html
1. 添加跳转登录页面地址
<form class="form-signin" th:action="@{/user/login}" method="post">
2. 添加显示用户名密码错误信息
<p th:text="${msg}" style="color:red" th:if="${!#strings.isEmpty(msg)}"></p>
3. 全部代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<!--配置跳转路径-->
<form class="form-signin" th:action="@{/user/login}" method="post">
<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 in</h1>
<!--错误显示信息-->
<p th:text="${msg}" style="color:red" th:if="${!#strings.isEmpty(msg)}"></p>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" name="password" class="form-control" 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-pr
imary btn-block" type="submit">[[#{login.btn}]]
</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(language='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(language='en_US')}">English</a>
</form>
</body>
</html>
2. 添加登录控制器
路径:src/main/java/com/xxx/controller/LoginController.java
代码:
package com.xxx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class LoginController {
@RequestMapping("/user/login")
// @ResponseBody
//@Controller + @ResponseBody = restController
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model) {
//具体业务
if(StringUtils.hasLength(username) && "123456" .equals(password)){
// return "dashboard";
return "redirect:/main.html";
}else{
//告诉用户登录失败
model.addAttribute("msg","用户名或者密码错误");
return "index";
}
}
}
3. 配置跳转路由为main.html
路径:src/main/java/com/xxx/config/MyMvcConfig.java
代码:
package com.xxx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//全面扩展springmvc,如果我们要扩展springmvc,官方建议我们这样做
@Configuration
public class MyMvcConfig 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();
}
}
4. 测试
路径:http://127.0.0.1:8080/
1. 密码错误测试
2. 成功跳转
5. 登录拦截器
1. 登录时添加session
路径:src/main/java/com/xxx/controller/LoginController.java
代码:
package com.xxx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
@RequestMapping("/user/login")
// @ResponseBody
//@Controller + @ResponseBody = restController
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model, HttpSession session) {
//具体业务
if(StringUtils.hasLength(username) && "123456" .equals(password)){
// 添加session
session.setAttribute("loginUser",username);
// return "dashboard";
return "redirect:/main.html";
}else{
//告诉用户登录失败
model.addAttribute("msg","用户名或者密码错误");
return "index";
}
}
}
2. 创建登录拦截器
路径:src/main/java/com/xxx/config/LoginHandlerInterceptor.java
代码:
package com.xxx.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//登录拦截器配置
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//return HandlerInterceptor.super.preHandle(request, response, handler);
//登录成功后,应该有用户的session;
Object loginUser = request.getSession().getAttribute("loginUser");
if(loginUser == null){//没有登录
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("index.html").forward(request,response);
}else{
return true;
}
return false;
}
}
3. 注册拦截器
路径:src/main/java/com/xxx/config/MyMvcConfig.java
代码:
package com.xxx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//全面扩展springmvc,如果我们要扩展springmvc,官方建议我们这样做
@Configuration
public class MyMvcConfig 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,首页,登录页,静态资源
.addPathPatterns("/**").excludePathPatterns("/index.html","/","/user/login","/static/*");
}
}
4. 登录成功后显示登录名
地址:src/main/resources/templates/commons/commons.html
代码
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
5. 测试
http://localhost:8080/main.html
1. 权限测试
直接登录main.html报错并跳转登录页
2. 成功显示登录名
3. 列表
1. 列表控制层
路径:src/main/java/com/xxx/controller/EmployeeController.java
代码:
package com.xxx.controller;
import com.xxx.dao.EmployeeDao;
import com.xxx.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Collection;
//员工管理
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps",employees);
return "emp/list";
}
}
2. 头部侧边栏页面分离
头部和侧边栏单独拿出来
分离标签:th:fragment=""
调用参数:[[${session.loginUser}]]
三元判断:${active=='main.html'?'nav-link active':'nav-link'}
路径:src/main/resources/templates/commons/commons.html
代码:
<!doctype html>
<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" th:href="@{/user/logout}">注销</a>
</li>
</ul>
</nav>
<!--侧边导航栏-->
<div class="container-fluid">
<div class="row">
<!--侧边导航栏-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<!-- 跳转到主界面
-->
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/main.html}">
<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-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
首页 <span class="sr-only">(current)</span>
</a>
</li>
<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-file">
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
<polyline points="13 2 13 9 20 9"></polyline>
</svg>
Orders
</a>
</li>
<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-shopping-cart">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
Products
</a>
</li>
<li class="nav-item">
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
<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>
员工管理
</a>
</li>
<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-bar-chart-2">
<line x1="18" y1="20" x2="18" y2="10"></line>
<line x1="12" y1="20" x2="12" y2="4"></line>
<line x1="6" y1="20" x2="6" y2="14"></line>
</svg>
Reports
</a>
</li>
<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-layers">
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
<polyline points="2 17 12 22 22 17"></polyline>
<polyline points="2 12 12 17 22 12"></polyline>
</svg>
Integrations
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Saved reports</span>
<a class="d-flex align-items-center text-muted" 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-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
</a>
</h6>
<ul class="nav flex-column mb-2">
<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-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Current month
</a>
</li>
<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-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Last quarter
</a>
</li>
<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-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Social engagement
</a>
</li>
<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-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Year-end sale
</a>
</li>
</ul>
</div>
</nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
<div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div>
</div>
<div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:200%;height:200%;left:0; top:0"></div>
</div>
</div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group mr-2">
<button class="btn btn-sm btn-outline-secondary">Share</button>
<button class="btn btn-sm btn-outline-secondary">Export</button>
</div>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
<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-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
This week
</button>
</div>
</div>
<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;"></canvas>
</main>
</div>
</div>
</html>
3. 列表页面
路径:src/main/resources/templates/emp/list.html
代码:
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<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>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<!--侧边栏-->
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a> </h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</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.getEmail()}"></td>
<td th:text="${emp.getGender()==0?'女':'男'}"></td>
<td th:text="${emp.getDepartment().getDepartmentName()}"></td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
<td >
<!--<a class="btn btn-sm btn-primary" th:href="@{/toUpdate/}+${emp.getId()}">编辑</a>-->
<!--
使用 <a th:href="@{/product/add}" target="_blank">产品</a>
可以得到 <a th:href="/product/add" target="_blank">产品</a>
使用 <a th:href="@{/product/show(skuid=${product.id})}" target="_blank">产品</a>
可以得到 <a th:href="/product/show?skuid=12" target="_blank">产品</a>
使用 <a th:href="@{'/product/show/'+${product.id}}" target="_blank">产品</a>
可以得到 <a th:href="/product/show/12" target="_blank">产品</a>
-->
<a class="btn btn-sm btn-primary" th:href="@{'/toUpdate/'+${emp.getId()}}">编辑</a>
<a class="btn btn-sm btn-danger" th:href="@{'/delemp/'+${emp.getId()}}">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="../../static/asserts/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" src="../../static/asserts/js/popper.min.js"></script>
<script type="text/javascript" src="../../static/asserts/js/bootstrap.min.js"></script>
<!-- Icons -->
<script type="text/javascript" src="../../static/asserts/js/feather.min.js"></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" src="../../static/asserts/js/Chart.min.js"></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
4. 测试
路径:http://localhost:8080/emps
4. 添加
1.list页面增加添加按钮
原先写了,这里提一下
路径:src/main/resources/templates/emp/list.html
代码:
<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a> </h2>
2. 控制页面
路径:src/main/java/com/xxx/controller/EmployeeController.java
添加页面代码:
package com.xxx.controller;
import com.xxx.dao.DepartmentDao;
import com.xxx.dao.EmployeeDao;
import com.xxx.pojo.Department;
import com.xxx.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Collection;
//员工管理
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
@Autowired
DepartmentDao departmentDao;
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps",employees);
return "emp/list";
}
//add页面
@RequestMapping("/emp")
public String toAddPage(Model model){
//查出所有部门信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/add";
}
//add提交页面
@PostMapping("/emp")
public String addEmp(Employee employee){
System.out.println("employee:"+employee);
//添加操作
employeeDao.save(employee);//调用底层业务方法保存员工信息
return "redirect:/emps";
}
}
3. 添加页面
路径:src/main/resources/templates/emp/add.html
代码:
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<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>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏-->
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form class="form-horizontal" th:action="@{/emp}" method="post">
<div class="form-group">
<label class="col-sm-2 control-label">名字</label>
<div class="col-sm-10">
<input 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 type="email" class="form-control" placeholder="1234567456@qq.com" 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 type="radio" name="gender" checked value="1"> 男
</label>
<label>
<input 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: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 type="text" class="form-control" placeholder="2000/11/11" 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>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="../../static/asserts/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" src="../../static/asserts/js/popper.min.js"></script>
<script type="text/javascript" src="../../static/asserts/js/bootstrap.min.js"></script>
<!-- Icons -->
<script type="text/javascript" src="../../static/asserts/js/feather.min.js"></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" src="../../static/asserts/js/Chart.min.js"></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
4. 日期格式化修改
路径:src/main/resources/application.properties
代码:
# 关闭模板引擎的缓存
spring.thymeleaf.cache=false
## 配置文件的真是位置
spring.messages.basename=i18n.login
## 日期格式化
spring.mvc.format.date=yyyy-MM-dd
5. 测试
点击添加员工
输入添加信息提交
提交成功返回
5. 编辑、删除
1. 列表页面编辑、删除按钮
上面已经添加,单独拿出来说明
路径:src/main/resources/templates/emp/list.html
代码:
<td >
<!--<a class="btn btn-sm btn-primary" th:href="@{/toUpdate/}+${emp.getId()}">编辑</a>-->
<!--
使用 <a th:href="@{/product/add}" target="_blank">产品</a>
可以得到 <a th:href="/product/add" target="_blank">产品</a>
使用 <a th:href="@{/product/show(skuid=${product.id})}" target="_blank">产品</a>
可以得到 <a th:href="/product/show?skuid=12" target="_blank">产品</a>
使用 <a th:href="@{'/product/show/'+${product.id}}" target="_blank">产品</a>
可以得到 <a th:href="/product/show/12" target="_blank">产品</a>
-->
<a class="btn btn-sm btn-primary" th:href="@{'/toUpdate/'+${emp.getId()}}">编辑</a>
<a class="btn btn-sm btn-danger" th:href="@{'/delemp/'+${emp.getId()}}">删除</a>
</td>
2. 控制层代码
路径:src/main/java/com/xxx/controller/EmployeeController.java
代码:
package com.xxx.controller;
import com.xxx.dao.DepartmentDao;
import com.xxx.dao.EmployeeDao;
import com.xxx.pojo.Department;
import com.xxx.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Collection;
//员工管理
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
@Autowired
DepartmentDao departmentDao;
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps",employees);
return "emp/list";
}
//add页面
@RequestMapping("/emp")
public String toAddPage(Model model){
//查出所有部门信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/add";
}
//add提交页面
@PostMapping("/emp")
public String addEmp(Employee employee){
System.out.println("employee:"+employee);
//添加操作
employeeDao.save(employee);//调用底层业务方法保存员工信息
return "redirect:/emps";
}
//修改页面
@RequestMapping("/toUpdate/{id}")
public String toUpdateEmp(@PathVariable("id")Integer id, Model model){
System.out.println("toUpdate:"+id);
//查询当前用户信息
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("employee",employee);
//查出所有部门信息
//查出所有部门信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/update";
}
//修改提交
@PostMapping("/updateUser")
public String updateUser(Employee employee){
System.out.println("updateUser:"+employee);
//修改操作
employeeDao.save(employee);//调用底层业务方法保存员工信息
return "redirect:/emps";
}
//删除
@RequestMapping("/delemp/{id}")
public String delEmp(@PathVariable("id")Integer id){
System.out.println("delemp:"+id);
employeeDao.delete(id);
return "redirect:/emps";
}
}
3. 编辑页面
路径:src/main/resources/templates/emp/update.html
代码
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<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>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏-->
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form class="form-horizontal" th:action="@{/updateUser}" method="post">
<input type="hidden" name="id" th:value="${employee.getId()}">
<div class="form-group">
<label class="col-sm-2 control-label">名字</label>
<div class="col-sm-10">
<input th:value="${employee.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="${employee.getEmail()}" type="email" class="form-control" placeholder="1234567456@qq.com" 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="${employee.getGender()==1}" type="radio" name="gender" checked value="1"> 男
</label>
<label>
<input th:checked="${employee.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="${employee.getDepartment().getId()==dept.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(employee.getBirth(),'yyyy-MM-dd HH:mm')}" type="text" class="form-control" placeholder="2000/11/11" 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>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="../../static/asserts/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" src="../../static/asserts/js/popper.min.js"></script>
<script type="text/javascript" src="../../static/asserts/js/bootstrap.min.js"></script>
<!-- Icons -->
<script type="text/javascript" src="../../static/asserts/js/feather.min.js"></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" src="../../static/asserts/js/Chart.min.js"></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
4. 测试
1. 编辑测试
点击编辑按钮
修改性别,点击提交
跳转列表
2. 删除测试
删除成功
6.404页面
无需配置,直接创建页面,自动识别出错跳转,500也是创建500.html
路径:src/main/resources/templates/error/404.html
代码:
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<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>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company name</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 out</a>
</li>
</ul>
</nav>
<div class="container-fluid">
<div class="row">
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" 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-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
Dashboard <span class="sr-only">(current)</span>
</a>
</li>
<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-file">
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
<polyline points="13 2 13 9 20 9"></polyline>
</svg>
Orders
</a>
</li>
<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-shopping-cart">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
Products
</a>
</li>
<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" 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-bar-chart-2">
<line x1="18" y1="20" x2="18" y2="10"></line>
<line x1="12" y1="20" x2="12" y2="4"></line>
<line x1="6" y1="20" x2="6" y2="14"></line>
</svg>
Reports
</a>
</li>
<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-layers">
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
<polyline points="2 17 12 22 22 17"></polyline>
<polyline points="2 12 12 17 22 12"></polyline>
</svg>
Integrations
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Saved reports</span>
<a class="d-flex align-items-center text-muted" 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-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
</a>
</h6>
<ul class="nav flex-column mb-2">
<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-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Current month
</a>
</li>
<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-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Last quarter
</a>
</li>
<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-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Social engagement
</a>
</li>
<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-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Year-end sale
</a>
</li>
</ul>
</div>
</nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h1>404</h1>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="../../static/asserts/js/jquery-3.2.1.slim.min.js" ></script>
<script type="text/javascript" src="../../static/asserts/js/popper.min.js" ></script>
<script type="text/javascript" src="../../static/asserts/js/bootstrap.min.js" ></script>
<!-- Icons -->
<script type="text/javascript" src="../../static/asserts/js/feather.min.js" ></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" src="../../static/asserts/js/Chart.min.js" ></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
7.退出登录
1. 修改页面注销
路径:src/main/resources/templates/commons/commons.html
<li class="nav-item text-nowrap">
<a class="nav-link" th:href="@{/user/logout}">注销</a>
</li>
2. 注销代码
路径:src/main/java/com/xxx/controller/LoginController.java
代码:
@RequestMapping("/user/logout")
public String logout(HttpSession session){
session.invalidate();
return "redirect:/index.html";
}
八、 项目开发梳理
- 前端搞定:页面长什么样子, 数据
- 设计数据库(数据库设计难点)
- 前端让他能够自动运行,独立化工程
- 数据接口对接:json ,对象
- 前后端联调测试
- 有一套自己熟悉的后台模板:工作必要 !X-admin官网(停止维护),X-admin Git网址 ,ruoyi官网
- 前端界面:至少自己能够通过前端框架,组合出来一个网页页面【 index about blog post user】
九、JDBC
1. 配置环境
1. 新建项目
创建空文件夹->file->new project->location选中创建的文件夹->配置信息->next
添加依赖关系->finsh
2. 查看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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xxx</groupId>
<artifactId>springboot-04-data</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-04-data</name>
<description>springboot-04-data</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. 配置数据库链接文件
1. 创建并配置文件
路径:src/main/resources/application.yaml
代码:
spring:
datasource:
username: root
password: root
#加入时区报错了,就增加一个时区的配置就ok了serverTimezone
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver #com.mysql.jdbc.Driver
2. 测试
路径:src/test/java/com/xxx/Springboot04DataApplicationTests.java
代码:
package com.xxx;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootTest
class Springboot04DataApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看默认数据源:class com.zaxxer.hikari.HikariDataSource
System.out.println(dataSource.getClass());
//获得数据库链接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//关闭
connection.close();
}
}
2. 增删改查
1. 配置web依赖
路径:pom.xml
代码:
<!-- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.18.RELEASE</version>
</dependency>
2. 创建增删改查controller
路径:src/main/java/com/xxx/controller/JDBCController.java
代码:
package com.xxx.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController//导入mvc使用
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
//查询数据库的所有信息
@GetMapping("/userList")
public List<Map<String, Object>> userList() {
String sql = "select * from user";
List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
return list_maps;
}
@GetMapping("/addUser")
public String addUser(){
String sql ="insert into mybatis.user(id ,name ,pwd) value(5,'小明','123456')";
jdbcTemplate.update(sql);
return "addUser-ok";
}
@GetMapping("/editUser/{id}")
public String editUser(@PathVariable("id")Integer id){
String sql ="update mybatis.user set name=?,pwd=? where id = "+id;
//封装
Object[] objects = new Object[2];
objects[0] = "小明";
objects[1] = "zzzz";
jdbcTemplate.update(sql,objects);
return "editUser-ok";
}
@GetMapping("/delUser/{id}")
public String delUser(@PathVariable("id")Integer id){
String sql ="delete from mybatis.user where id = ?";
jdbcTemplate.update(sql,id);
return "delUser-ok";
}
}
3. 测试
注意:运行启动文件,不要运行测试文件
1. 列表
访问:http://localhost:8080/userList
2. 添加
访问:http://localhost:8080/addUser
3. 修改
访问:http://localhost:8080/editUser/5
4. 删除
访问:http://localhost:8080/delUser/5
十、Druid
1. Druid依赖
狂神说笔记:狂神说Druid笔记
mvnrepository:https://mvnrepository.com/search?q=Druid
pom 引入刷新
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
2. Druid配置
1. 配置
路径:src/main/resources/application.yaml
代码:
spring:
datasource:
username: root
password: root
#加入时区报错了,就增加一个时区的配置就ok了serverTimezone
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver #com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
2. 测试
路径:src/test/java/com/xxx/Springboot04DataApplicationTests.java
代码:
package com.xxx;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootTest
class Springboot04DataApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看默认数据源:class com.zaxxer.hikari.HikariDataSource
System.out.println(dataSource.getClass());
//获得数据库链接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//关闭
connection.close();
}
}
运行:
3.设置数据源其他参数
路径:src/main/resources/application.yaml
代码:
spring:
datasource:
username: root
password: root
#加入时区报错了,就增加一个时区的配置就ok了serverTimezone
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver #com.mysql.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
4. 导入Log4j 的依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
5. 配置数据源监控
1. 创建配置文件
路径:src/main/java/com/xxx/config/DruidConfig.java
代码:
package com.xxx.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import javax.sql.DataSource;
import java.util.HashMap;
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
//因为springboot 内置了servlet 容器,所以没有web.xml,使用web.xml替代方法
public DataSource druidDataSource() {
return new DruidDataSource();
}
//后台监控:web.xml ServletRegistrationBean
@Bean
public ServletRegistrationBean StatViewServlet() {
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要有人登录
HashMap<String, String> initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername","admin");//登录key 固定的loginUsername loginPassword
initParameters.put("loginPassword","123456");
//允许谁可以访问
initParameters.put("allow","");//第二个参数为空是所有人可以访问
//禁止谁能访问
// initParameters.put("ff","192.168.11.123");//禁止这个ip访问
bean.setInitParameters(initParameters);//设置初始化参数
return bean;
}
//filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
//可以过滤那些请求呢
HashMap<String, String> initParameters = new HashMap<>();
//这些东西不进行统计
initParameters.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParameters);
return bean;
}
}
2. 启动
启动文件启动
3. 访问
访问:http://localhost:8080/druid/login.html
用户名和密码配置的这两个:
//增加配置
initParameters.put("loginUsername","admin");//登录key 固定的loginUsername loginPassword
initParameters.put("loginPassword","123456");
登录成功:
4. 测试
访问:http://localhost:8080/userList
查看sql监控
十一、Mybatis
1. 搭建环境
1. 创建项目
File->New->new project
选择依赖,点击FINISH
2. 引入依赖
MyBatis Spring Boot Starter仓库地址:https://mvnrepository.com/search?q=MyBatis+Spring+Boot+Starter
MyBatis Spring Boot Starter » 2.2.1代码:
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
pom.xml导入依赖
<dependencies>
<!--mybatis 整合-->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--spring-boot-starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3. 链接数据库
1. 配置文件
路径:src/main/resources/application.properties
代码:
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone-UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
2. 测试
路径:src/test/java/com/xxx/Springboot05MybatisApplicationTests.java
代码:
package com.xxx;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.SQLException;
@SpringBootTest
class Springboot05MybatisApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//class com.zaxxer.hikari.HikariDataSource
System.out.println(dataSource.getClass());
//HikariProxyConnection@262093096 wrapping com.mysql.cj.jdbc.ConnectionImpl@404eca05
System.out.println(dataSource.getConnection());
}
}
2. 增删改查实现
1. pom导入lombok
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2. 创建user实体类
路径:src/main/java/com/xxx/pojo/User.java
package com.xxx.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
3. 创建mapper.xml
路径:src/main/resources/mybatis/mapper/UserMapper.xml
代码:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxx.mapper.UserMapper">
<select id="queryUserList" resultType="User">
select *
from user
</select>
<select id="queryUserById" resultType="User" parameterType="int">
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="DeleteUser" parameterType="int">
delete
from user
where id = #{id}
</delete>
</mapper>
4. 配置mapper路径
路径:src/main/resources/application.properties
代码:
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone-UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 整合mybatis
mybatis.type-aliases-package=com.xxx.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
5. 控制器
路径:src/main/java/com/xxx/controller/UserController.java
代码:
package com.xxx.controller;
import com.xxx.mapper.UserMapper;
import com.xxx.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/queryUserList")
public List<User> queryUserList() {
List<User> users = userMapper.queryUserList();
for (User user : users) {
System.out.println(user);
}
return users;
}
}
6. 测试
运行启动页
访问路径:http://localhost:8080/queryUserList