SpringBOOT(常用配置,注解,整合mybatis,thymeleaf,aop,部署,文件上传下载,拦截器,异常处理,跨域,日志)
初始化配置SpringBOOT介绍SpringBOOT特点快速搭建SpringBOOT选择构建的项目的类型项目的描述指定SpringBoot版本和需要的依赖修改默认的依赖编写controller运行,直接运行默认创建的类的main方法即可SpringBOOT的项目结构pom.xml文件.gitignore文件src目录SpringBoot三种启动方式运行启动类的main方法maven命令采用jar
初始化配置
- SpringBOOT介绍
- 快速搭建SpringBOOT
- SpringBOOT的项目结构
- SpringBoot三种启动方式
- 自定义SpringApplication
- 依赖管理
- SpringBOOT常用注解
- SpringBOOT常用配置
- 单元测试
- SpringBoot日志使用
- web开发
- SpringBOOT整合JDBC
- SpringBOOT整合Druid
- SpringBOOT整合mybatis
- 注解方式整合mybatis
- SpringBOOT整合JSP
- Spring Boot整合Thymeleaf 模版
- AOP面向切面编程
- 部署
- ⽂件上传
- 文件下载
- 拦截器 interceptor
- 统一异常处理
- CORS 跨域
- 日志
springboot重要的自动装配我专门写了另一篇博客,一开始学看了许多遍都没学懂,后来才感觉有一点体会了springboot自动装配原理分析链接
SpringBOOT介绍
SpringBOOT不是一门新技术,只是将之前常用的spring,springMVC,data-jpa等常用的框架封装到了一起,帮助你影藏了这些框架的整合细节,实现敏捷开发
SpringBoot是整合Spring技术栈的一站式框架
SpringBoot是简化Spring技术栈的快速开发脚手架
SpringBOOT特点
SpringBOOT项目不需要模板化的配置
SpringBOOT中默认整合第三方框架时,只需要导入响应的starter依赖包,就会自动整合了
SpringBOOT默认只有一个.properties的配置文件,不推荐使用xml,后期会采用.java的文件去编写配置信息
SpringBOOT工程在部署时,采用的是jar包的方式,内部自动依赖Tomcat容器,提供了多环境配置
后期要学习的微服务框架SpringClod需要建立在SpringBOOT基础上
SpringBOOT 优点
--快速构建一个独立的 Spring 应用程序 ;
--嵌入的 Tomcat 、 Jetty 或者 Undertow,无须部署 WAR 文件;
--提供starter POMs来简化Maven配置和减少版本冲突所带来的问题;
--对Spring和第三方库提供默认配置,也可修改默认值,简化框架配置;
--提供生产就绪型功能,如指标、健康检查和外部配置;
--无需配置XML,无代码生成,开箱即用;
SpringBoot框架中还有两个非常重要的策略:开箱即用和约定优于配置。
-
开箱即用(Outofbox),是指在开发过程中,通过在MAVEN项目的pom文件中添加相关依赖包,然后使用对应注解来代替繁琐的XML配置文件以管理对象的生命周期。这个特点使得开发人员摆脱了复杂的配置工作以及依赖的管理工作,更加专注于业务逻辑。
-
约定优于配置(Convention over configuration),是一种由SpringBoot本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化
快速搭建SpringBOOT
选择构建的项目的类型
项目的描述
指定SpringBoot版本和需要的依赖
修改默认的依赖
<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>
在主程序的同级目录下,新建一个controller包,一定要在同级目录下或子目录下,否则识别不到
完整最简单的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>
<!-- 父依赖 继承springboot的父项目 -->
<!-- 这是Spring Boot的父级依赖,这样当前的项目就是Spring Boot项目了。
2 它用来提供相关的Maven默认依赖。使用它之后,常用的包依赖可以省去version标签。 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.blb</groupId>
<artifactId>first-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>first-springboot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web场景启动器 引入springboot的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>
</dependency>
<build>
<plugins>
<!-- 打包插件 -->
<!-- 这个插件,可以将应用打包成一个可执行的jar包;-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter
相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器
编写controller
package com.blb.firstspringboot.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/test")
public String test(){
return "hello SpringBOOT";
}
}
运行,直接运行默认创建的类的main方法即可
package com.blb.firstspringboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
自动装配就是从这里开始的
*/
@SpringBootApplication
public class FirstSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(FirstSpringbootApplication.class, args);
}
}
SpringBOOT的项目结构
pom.xml文件
指定了一个父工程:指定了当前工程为SpringBoot,帮助我们声明了starter依赖的版本
项目的元数据:包名,项目名,版本号
指定了properties信息:指定了java的版本为1.8
导入了依赖:默认情况导入spring-boot-starter,spring-boot-starter-test
插件:spring-boot-maven-plugin
.gitignore文件
默认帮我们忽略了一些文件和目录,避免提交到Git仓库
src目录
-src
-main
-java
-包名
启动类.java #需要将Controller类,放在启动类的子包中或者同级包下
-resources
-static #存放静态资源的
-templates #存放模板页面的
application.properties #SpringBOOT提供的唯一配置文件
-test #只是为了测试用的
Spring Boot默认jar包使用嵌式的Tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf)
默认的包结构
• 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
• 无需以前的包扫描配置
spring底层在解析配置类,会去解析@ComponentScan读取basePackages
如果没有读取到,会将当前配置类所在的包当做扫描包,
位置最好放在需要扫描包的根目录或者说放在所有bean的顶层目录中
想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.blb”)
• 或者@ComponentScan 指定扫描路径
SpringBoot三种启动方式
运行启动类的main方法
运行自动生成启动类的main方法即可
maven命令
mvn spring-boot:run
采用jar包的方式运行
首先在Terminal
输入命令 mvn clean package
等它生成jar包然后在target里面的test-classes找到这个jar包
通过java -jar jar文件名
自定义SpringApplication
可以创建一个本地实例并对其进行自定义。例如,要关闭横幅,您可
以编写:
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MySpringConfiguration.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}
依赖管理
依赖管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
他的父项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
也解决了第三方库直接的版本冲突问题
无需关注版本号,自动版本仲裁
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。
可以修改默认版本号
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
<properties>
<mysql.version>8.0.20</mysql.version>
</properties>
SpringBOOT常用注解
@Configuration,@Bean
在之前使用SSM去开发时,在xml文件中编写bean标签,但是SpringBoot不推荐使用
xml文件
@Configuration 注解相当于beans标签
@Configuration:告诉SpringBoot 这是一个配置类 == 配置文件 == 容器
@Bean 注解相当于bean标签
@Bean:给容器添加一个组件,方法名为组件id,返回类型就是组件类型,返回值是组
件在容器中的实例
bean标签的id属性=方法名 注解中的name属性优先级更高
bean标签的class属性= 方法的返回结果
配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
配置类本身也是容器的组件
proxyBeanMethods:代理bean的方法
Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件
都是单实例的】
Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是
新创建的】
组件依赖必须使用Full模式默认。其他默认是否Lite模式
如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot
总会检查这个组件是否在容器中有
配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
package com.blb.firstspringboot.config;
import com.blb.firstspringboot.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //代表当前类是一个配置类
public class UserConfig {
@Bean(name="user1")//构建一个实例,放到spring容器中
//给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user(){
User user=new User();
user.setId(1);
user.setName("dyk");
return user;
}
// 相当于
// <beans...> @Configuration相当于beans标签
// <bean id="user1" class="com.blb.firstspringboot.entity.User" />
// @Bean 相当于bean标签 方法名相当于id 方法的返回值相当于对应的class
// 默认的bean的id是方法名,或者使用注解里的name属性来改变bean的id名字
// </beans>
}
@ComponentScan
该注解默认会扫描该类所在的包下所有的配置类,相当于之前的
<context:component-scan>
一般在配置类上添加,启动类中配置的默认位置为启动类所在包
@SpringBootApplication注解
@SpringBootApplication注解就是一个组合注解
1.@SpringBootConfiguration就是@Configuration注解,代表启动类就是一个配置类
2.@EnableAutoConfiguration 帮你实现自动装配,SpringBoot工程启动时,运行一个SpringFactoriesLoader的类,加载META-INF/spring.factories配置类(已经开启的)通过SpringFactoriesLoader中的load方法,以for循环的方式,一个一个加载
好处:无需编写大量整合配置信息,只需要按照SpringBoot提供好的约定去整合即可
坏处:如果你导入了一个start依赖,那么你就需要填写他必要的配置信息
手动关闭自己动装配指定内容@SpringBootApplication(exclude=QuartAutoConfiguration.class)
3.@ComponentScan就相当于<context:component-scan basePackage="包名" /> 帮助扫描注解
注入相关的注解
@Component
@Configuration @Bean
@Service @Controller @Repository
@Import
@Import
使用@import导入的bean的名字默认组件的名字就是全类名
1.导入Bean实体类
2.导入配置类
3.导入importSelector实现类,一般用于加载配置文件中的类
4.导入xxx实现类
// 直接引入一个类
@Import(User.class)
//加载类,这些类都会被spring创建,并放入IOC容器
// 引入一个配置类
@Import(UserConfiguration.class)
实现了ImportSelector接口
// 引入一个实现了ImportSelector接口的类,返回全类型数组,数组的类都可以引入
@Import(UserSelector.class)
public class UserSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata)
{
return new String[]{"com.blb.entity.User","com.blb.entity.Book"};
}
}
实现ImportBeanDefinitionRegistrar
public class MyImportBeanDefinitionRegistarar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
registry.registerBeanDefinition("user",beanDefinition);
//相当于在ioc容器中注册一个id为user类型为User的bean
}
}
@ImportResource
原生配置文件引入
作用:给容器中导入指定资源(原生配置引入:用于新旧项目改造,第三方资源)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="sg" class="com.kgc.hello.entity.User">
<property name="name" value="张三"/>
<property name="age" value="18"/>
</bean>
<bean id="hc" class="com.kgc.hello.entity.Car">
<constructor-arg value="兰博基尼"/>
</bean>
</beans>
@ImportResource("classpath:beans.xml")
@Conditional
条件装配:满足Conditional指定的条件,则进行组件注入
注解在类上方,表示范围为类,在方法上,便是范围为方法
注解名 | 作用 |
---|---|
@Conditional扩展注解作用 | (判断是否满足当前指定条件) |
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 满足指定SpEL表达式 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
注释@Bean注解
//@Bean("兰博基尼")
public Car car01() {
return new Car("兰博基尼");
}
boolean user01 = run.containsBean("user01");
System.out.println("容器中user01组件:"+user01);
boolean car = run.containsBean("兰博基尼");
System.out.println("容器中兰博基尼组件:"+car);
结果
容器中user01组件:true
容器中兰博基尼组件:false
添加@ConditionalOnBean注解
ConditionalOnBean(name = "兰博基尼")
@Bean // 给容器添加一个组件,方法名为组件id,返回类型就是组件类型,返回值是
组件在容器中的实例
public User user01() {
User user = new User("zhangsan",18);
user.setCar(car01());
return user;
}
再次查看结果
容器中user01组件:false
容器中兰博基尼组件:false
绑定配置
Java读取到配置文件中的内容,并且把它封装到JavaBean中
@ConfigurationProperties
aliyun:
key: 110
id: 123
pwd: 456
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = "aliyun" : 将配置文件中的aliyun下面的所有属性一一对应
*/
@ConfigurationProperties(prefix = "aliyun")
@Component
//只有在容器中的组件,才会拥有SpringBoot提供的强大功能
public class AliyunProperties {
private String key;
private String id;
private String pwd;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
@EnableConfigurationProperties + @ConfigurationProperties
EnableConfigurationProperties(AliyunProperties.class)
//功能:
//1.开启配置绑定的功能
//2.把这个组件注册到容器中
使用了EnableConfigurationProperties就不用在使用component注入到容器中了,因为有些时候的类可能是引用的第三方包里面的,我们就不能手动给别人加component注解
SpringBOOT常用配置
SpringBOOT的配置文件格式
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
springboot 启动还会扫描以下位置的application.properties或application.yml文件作为Spring boot的默认配置文件
application.properties
语法结构 :key=value
application.yml
语法结构 :key:空格 value
SpringBoot的配置文件支持properties和yml,甚至还支持json
但是推荐使用yml
yml的优点:
1.yml文件,会根据换行和缩进帮助咱们管理配置文件所在位置
2.yml文件,相比properties更轻量级一些
yml缺点
1.严格遵循换行和缩进
2.在填写value时,一定要在:后面跟上空格
yaml基础语法
说明:语法要求严格!
1、空格不能省略,缩进时不允许使用tab键,只能使用空格
2、缩进的空格数目不重要,只要相同层级的左侧对齐即可
3、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
4、属性和值的大小写都是十分敏感的
5、如果有特殊字符% & 记得用单引号包起来
6、 #表示注释,从这个字符一直到行尾,都会被忽略
字面量:普通的值 [ 数字,布尔值,字符串 ]
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
k: v
注意:
“ ” 双引号,会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: “dyk \n 666” 输出 :dyk 换行 666
‘’ 单引号,不会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘dyk \n 666’ 输出 :dyk \n 666
对象、Map(键值对)
#对象、Map格式
k:
v1:
v2:
在下一行来写对象的属性和值得关系,注意缩进;比如:
student:
name: dyk
age: 18
行内写法
student: {name: dyk,age: 3}
数组( List、set )
用 - 值表示数组中的一个元素,比如:
pets:
- cat
- dog
- pig
行内写法
pets: [cat,dog,pig]
纯量
纯量是最基本的、不可再分的值。字符串、布尔值、整数、浮点数、Null、时间、日期都属于纯量
数值直接以字面量的形式表示
age: 18
布尔值用true或者false表示
marry: true
null用~表示
object: ~
时间
date: 2020-04-17
#时间跟日期之间用-连接
datetime: 2020-04-17T15:50:10
字符串
#字符串默认不使用引号表示
name: seven
# 如果字符串中包含了空格或者特殊字符,需要包含在引号中
name: 'se ven '
# 双引号也可以,只是双引号不会对特殊字符转义
name: 'se \n ven '
# 单引号之中如果还有单引号,必须连续使用两个单引号转义。
hello: 'my''name is seven'
#字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格。
name:
Seven
Hu
#Seven Hu
# 多行字符串可以使用|保留换行符
name: |
Seven
Hu
# Seven\nHu\n
# 多行字符串可以使用>折叠换行
name: >
Seven
Hu
#Seven Hu\n
#字符串之中可以插入 HTML 标记。
message: |
<p style="color: red">
段落
</p>
# message: '\n<p style="color: red">\n 段落\n</p>\n
yaml参数引用
name: dyk
person:
name:${name} #引用上边定义的name值
引用
锚点&
和别名*
,可以用来引用。
# 设置person性别,通过&设置锚点,锚点名为default(diy)
person: &default
sex: 男
student:
name: seven
age: 18
<<: *default
# <<表示合并到当前数据 , *用来引用锚点。
pets:
- &d dog # 给第一个元素定义锚点d
- cat
- pig
- *d # 引用上面定义的锚点d
yaml文件中还可以使用SPEL表达式
#随机获取int类型的数
${random.int}
#随机获取一段uuid
${random.uuid}
#随机获取指定范围的数字
${random.int(0,10)}
#引用前面配置过的属性,如果没有就用默认值
${student.name:默认值}
多环境配置
在application.yml中添加一个配置项
application.yml
spring:
profiles:
#环境名只需要填后缀即可
active: dev
application-dev.yml
生产环境
server:
#项目端口
port: 8080
servlet:
#项目路径
context-path: /boot
application-prov.yml
开发环境
server:
#项目端口
port: 8080
servlet:
#项目路径
context-path: /
或者使用yaml的多文档块
和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件
用三个-来分隔
server:
port: 8081
#选择要激活那个环境块
spring:
profiles:
active: prod
---
server:
port: 8083
spring:
profiles: dev #配置环境的名称
---
server:
port: 8084
spring:
profiles: prov #配置环境的名称
先按照位置来读取优先级, 在同一位置下profile优先级最高, 如果没有指定profile, 先yml‐‐yaml‐‐properties
在resource目录下,创建多个application-环境名.yml文件即可
在部署工程时,通过java -jar jar文件名 --spring.profiles.active=环境名
临时属性设置
带属性启动SpringBoot
java -jar springboot.jar --属性名=值
java -jar springboot.jar --server.port=80
携带多个属性启动SpringBoot,属性间使用空格分隔
激活指定profile
1. 在配置文件中指定 spring.profiles.active=dev
2. 命令行
java ‐jar configuration_file‐0.0.1‐SNAPSHOT.jar ‐‐spring.profiles.active=dev;
通过spring.config.location来改变默认的配置文件
使用spring.config.location 指定的配置文件, 是不会进行互补
java ‐jar configuration_file‐0.0.1‐SNAPSHOT.jar ‐‐spring.config.location=D:/application.properties
配置文件加载位置优先级
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
如果同时存在不同后缀的文件按照这个顺序加载主配置文件;互补配置;
优先级1:项目根目录路径下的config文件夹配置文件
优先级2:项目根目录路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置,互补配置;
如果当前项目是继承/耦合 关系maven项目的话,项目根目录=父maven项目的根目录
一般使用的是优先级3或者优先级4,因为maven默认打包的会忽略前两种配置
改变默认的启动banner
在resources目录下创建一个banner.txt,当文件下面有一个小记号则成功了
图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中即可
Charizard
."-,.__
`. `. ,
.--' .._,'"-' `.
. .' `'
`. / ,'
` '--. ,-"'
`"` | \
-. \, |
`--Y.' ___.
\ L._, \
_., `. < <\ _
,' ' `, `. | \ ( `
../, `. ` | .\`. \ \_
,' ,.. . _.,' ||\l ) '".
, ,' \ ,'.-.`-._,' | . _._`.
,' / \ \ `' ' `--/ | \ / / ..\
.' / \ . |\__ - _ ,'` ` / / `.`.
| ' .. `-...-" | `-' / / . `.
| / |L__ | | / / `. `.
, / . . | | / / ` `
/ / ,. ,`._ `-_ | | _ ,-' / ` \
/ . \"`_/. `-_ \_,. ,' +-' `-' _, ..,-. \`.
. ' .-f ,' ` '. \__.---' _ .' ' \ \
' / `.' l .' / \.. ,_|/ `. ,'` L`
|' _.-""` `. \ _,' ` \ `.___`.'"`-. , | | | \
|| ,' `. `. ' _,...._ ` | `/ ' | ' .|
|| ,' `. ;.,.---' ,' `. `.. `-' .-' /_ .' ;_ ||
|| ' V / / ` | ` ,' ,' '. ! `. ||
||/ _,-------7 ' . | `-' l / `||
. | ,' .- ,' || | .-. `. .' ||
`' ,' `".' | | `. '. -.' `'
/ ,' | |,' \-.._,.'/'
. / . . \ .''
.`. | `. / :_,'.'
\ `...\ _ ,'-. .' /_.-'
`-.__ `, `' . _.>----''. _ __ /
.' /"' | "' '_
/_|.-'\ ,". '.'`__'-( \
/ ,"'"\,' `/ `-.|" mh
引入外部配置文件的信息
方式一 @Value("${xxx}")
通过@Value注解去获取properties/yml文件中的内容
在yml中定义一个路径
server:
#项目端口
port: 8081
servlet:
#项目路径
context-path: /
picPath: D:\images\img
package com.blb.firstspringboot.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@Value("${picPath}")
private String picPath;
@GetMapping("/picPath")
public String picPath(){
return picPath;
}
}
方式二@ConfigurationProperties
如果在yml文件中需要编写大量的自定义配置,并且具有统一的前缀时,采用如下方式
.yml
aliyun:
key: 110
id: 123
pwd: 456
通过注解@ConfigurationProperties(prefix=“配置文件中的key的前缀”)可以将配置文件中的配置自动与
实体进行映射
package com.blb.firstspringboot.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = "aliyun" : 将配置文件中的aliyun下面的所有属性一一对应
*/
@ConfigurationProperties(prefix = "aliyun")
@Component
public class AliyunProperties {
private String key;
private String id;
private String pwd;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
注意
将配置文件中配置的每一个属性的值,映射到这个组件中
@ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相
关的配置进行绑定;
prefix = "aliyun":配置文件中哪个下面的所有属性进行一一映射
只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
配置提示
使用完这个注解可能会提示报错红色的
如果使用了@ConfigurationProperties注解,可以增加以下依赖,让我们在书写配置时有相应的提示。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
注意:添加完依赖加完注解后要运行一次程序才会有相应的提示。
package com.blb.firstspringboot.controller;
import com.blb.firstspringboot.properties.AliyunProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@Autowired
private AliyunProperties properties;
@GetMapping("/aliyun")
public AliyunProperties aliyun(){
return properties;
}
}
YAML配置映射
//注入到spring容器中
@Component
// 去默认的配置文件中找名称为student对象
@ConfigurationProperties(prefix = "student")
public class Student {
private String name ;
private Integer age ;
private Boolean marry ;
private Map<String,Object> info ;
private List<String> hobby;
private Book bookInfo ;
private Date birth ;
// setter getter toString
}
@Component
public class Book {
private Integer bookId ;
private String bookName;
private Double bookPrice ;
// setter getter toString
}
student:
name: seven
age: 18
marry: true
birth: 2019/01/22
#map类型
info: {height: 180,weight: 180}
# list类型
hobby:
- game
- coding
- girl
# 应用数据类型
book-info:
book-id: 101
book-name: java入门到精通
book-price: 100
顺便可以对比一下properties
@Component
@PropertySource("classpath:application-student.properties")
@ConfigurationProperties(prefix = "student")
public class Student {
private String name ;
@Value("${age}")
private Integer age ;
@Value("${marry}")
private Boolean marry ;
@Value("${birth}")
private Date birth ;
private Map<String,Object> info ;
private List<String> hobby;
private Book bookInfo ;
// setter getter toString
}
@Component
public class Book {
private Integer bookId ;
private String bookName;
private Double bookPrice ;
// setter getter toString
}
-
@PropertySource(“classpath:application-student.properties”)表示去找classpath下的application-student.properties配置文件。PropertySource默认不支持yaml。
-
@ConfigurationProperties(prefix = “student”)属性映射的时候配置前缀。
#使用前缀student可以自动映射
student.name=seven
#不使用前缀需要用SPEL表达式映射
student.age=18
student.marry=true
student.birth=2019/01/22
#给map类型复制
student.info.height=180
student.info.weight=180
#给list类型复制
student.hobby[0] = game
student.hobby[1] = coding
student.hobby[2] = girl
#给引用数据类型复制
student.bookInfo.bookId: 101
student.bookInfo.bookName: java入门到精通
student.bookInfo.bookPrice: 100
简单类型可以通过前缀方式也可以不用前缀,使用前缀需要保证字段命名的一致性,如果不使用前缀需要在实体类的属性通过注解@Value("${age}") 来映射
方式三 通过Environment变量
注意这个Environment有很多要是spring下的
package com.blb.firstspringboot.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class envtest {
@Autowired
private Environment env;
@RequestMapping("/env")
public String test(){
String picpath = env.getProperty("picpath");
return picpath;
}
}
松散绑定:
user:
USERNAME: dyk # 全大写
user:
userName: dyk #驼峰
user:
user_name: dyk # 蛇形 下划线
user:
user‐name: dyk #烧烤形 中划线
以上4种命名是可以自动绑定bean属性 User.username
@Value获取值和@ConfigurationProperties获取值比较
@ConfigurationProperties | @Value | |
---|---|---|
绑定 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定(松散语法) | 支持 | 支持有限 |
SpEL | 不支持 | 支持 |
自动提示 | 支持 | 不支持 |
配置文件yml还是properties他们都能获取到值;
使用场景
如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
如果说,有很多项我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;
YML和properties配置的相互转换
我们可以使用一些网站非常方便的实现YML和properties格式配置的相互转换。
转换网站:转化网站链接
JSR303数据校验
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //email必须是邮箱格式
private String email;
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
}
空检查
@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 对象是否符合正则表达式的规则
# 空检查
#验证对象是否为null,不包括长度为0的字符串
@Null
# 验证对象是否不为null,不包括长度为0的字符串
@NotNull(message="XXX")
# 验证约束字符串是不是null,包括Trim后的Size>0
@NotBlank
# 不能为null,且Size>0
@NotEmpty
#Boolean检查
# 验证boolean类型是否为true值
@AssertTrue
# 验证boolean类型是否为false值
@AssertFalse
#长度检查
# 验证对象(array、Collection、Map、String)长度是否在给定范围内
@Size(min=1,max=10)
# 验证对象(String)是否在范围内
@Length
#大小检查
# 最大范围不大于38
@Max(38)
# 最小范围不小于18
@Min(18)
# 数字验证,整数部分不超过3位,小数部分不超过2位
@Digits(integer=3,fraction = 2)
# 日期检查
# 验证Date或者Calendar对象是否在当前时间之前
@Past
# 验证Date或者Calendar对象是否在当前时间之后
@Future
# 验证String是否符合正则表达式规则
@Pattern
热部署
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
由于使用的是IDEA开发工具,添加热部署依赖后可能没有任何效果,接下来还需要针对IDEA开发工具进行热部署相关的功能设置
settings配置
选择Build下的Compiler选项,在右侧勾选“Build project automatically”选项将项目设置为自动编译,单击【Apply】→【OK】按钮保存设置
ctrl + shift + alt + / ,选择Registry,勾上 Compiler autoMake allow when app running
列表中找到“compiler.automake.allow.when.app.running”,将该选项后的Value值勾选,用于指定IDEA工具在程序运行过程中自动编译,最后单击【Close】按钮完成设置
重新构建工程
单元测试
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
@SpringBootTest
public class ApplicationTest {
@Autowired
private HelloController helloController;
@Test
public void testJunit()
{
System.out.println(1);
System.out.println(helloController);
}
注意:测试类所在的包需要和启动类是在同一个包下。否则就要使用如下写法指定启动类
//classes属性来指定启动类
@SpringBootTest(classes = HelloApplication.class)
public class ApplicationTest {
@Autowired
private HelloController helloController;
@Test
public void testJunit()
{
System.out.println(1);
System.out.println(helloController);
}
}
兼容老版本
- JUnit Platform: 这是Junit提供的平台功能模块,通过它,其它的测试引擎也可以接入
- JUnit JUpiter:这是JUnit5的核心,是一个基于JUnit Platform的引擎实现,它包含许多丰富的新
特性来使得自动化测试更加方便和强大。 - JUnit Vintage:这个模块是兼容JUnit3、JUnit4版本的测试引擎,使得旧版本的自动化测试也可
以在JUnit5下正常运行。
虽然Junit5包含了JUnit Vintage来兼容JUnit3和Junit4,但是SpringBoot 2.4 以上版本对应的springboot-starter-test移除了默认对 Vintage 的依赖。所以当我们仅仅依赖spring-boot-starter-test时会
发现之前我们使用的@Test注解和@RunWith注解都不能使用了
我们可以单独在依赖vintage来进行兼容。
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
注意:
org.junit.Test对应的是Junit4的版本,就搭配@RunWith注解来使用。
SpringBoot2.2.0之前版本的写法
import com.sangeng.controller.HelloController;
//import org.junit.jupiter.api.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
//classes属性来指定启动类
@SpringBootTest
@RunWith(SpringRunner.class)
public class ApplicationTest {
@Autowired
private HelloController helloController;
@Test
public void testJunit()
{
System.out.println(1);
System.out.println(helloController);
}
}
SpringBoot日志使用
springboot框架 集成⽇志 logback ⽇志
Logback是由log4j创始⼈设计的⼜⼀个开源⽇志组件。⽬前,logback分为三个模
块:logback-core,logback-classic和logback-access。是对log4j⽇志展示进⼀步改进
总结: logback 也是⼀个开源⽇志组件 和 log4j作⽤⼀致 都是⽤来⽣成⽇志
logback更加轻量
All < Trace <
DEBUG < INFO < WARN < ERROR
< Fatal < OFF
- OFF | 关闭:最⾼级别,不打印⽇志。
- FATAL | 致命:指明⾮常严重的可能会导致应⽤终⽌执⾏错误事件。
- ERROR | 错误:指明错误事件,但应⽤可能还能继续运⾏。
- WARN | 警告:指明可能潜在的危险状况。
- INFO | 信息:指明描述信息,从粗粒度上描述了应⽤运⾏过程。
- DEBUG | 调试:指明细致的事件信息,对调试应⽤最有⽤。
- TRACE | 跟踪:指明程序运⾏轨迹,⽐DEBUG级别的粒度更细。
- ALL | 所有:所有⽇志级别,包括定制级别。
⽇志级别由低到⾼:
⽇志级别越⾼输出的⽇志信息越多
可以设置TRACE,DEBUG,INFO,WARN,ERROR,FATAL或OFF之一
项⽬中⽇志分类
# ⽇志分类:
- ⼀种是rootLogger(根全局⽇志) : ⽤来监听项⽬中所有的运⾏⽇志 包括引⼊依赖
jar中的⽇志
- ⼀种是logger(指定包级别⽇志) : ⽤来监听项⽬中指定包中的⽇志信息
logging:
level:
root: debug #指定根⽇志级别(⼀般不推荐修改根⽇志,输出信息太多,推荐使⽤⼦⽇志)
com.blb.dao: debug #指定某个包中⽇志
file:
name: aaa.log #指定⽇志名称
path: ./ #指定⽇志⽂件⽬录
可以使用logging.pattern.console 修改默认的控制的日志格式:
%clr(%d{${LOG_DATEFORMAT_PATTERN:‐yyyy‐MM‐dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:‐%5p}) %c
lr(${PID:‐ }){magenta} %clr(‐‐‐){faint} %clr([%15.15t]){faint} %clr(%‐40.40logger{39}){cyan} %clr(:){fa
int} %m%n${LOG_EXCEPTION_CONVERSION_WORD:‐%wEx}
详细介绍
%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint}
%clr 当前内容的颜色 {faint}
(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}})括号中就是要显示的内容
%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}
${value:value2} springboot的占位符 + null条件的表达式(如果value为null 使用value2)
LOG_DATEFORMAT_PATTERN: 系统环境变量中的值,
springboot底层会根据对应的配置项将值设置到对应的环境变量中
%d{-yyyy-MM-dd HH:mm:ss.SSS}
%d logbak的日期显示方式:
{-yyyy-MM-dd HH:mm:ss.SSS} 日期的格式
文件输出
默认情况下,Spring Boot仅记录到控制台,不写日志文件。如果除了控制台输出外还想写日志文件,则需要设置一个logging.file.name或logging.file.path属性(例如,在中application.properties)
logging.file.name
可以设置文件的名称, 如果没有设置路径会默认在项目的相对路径下
还可以指定路径+文件名:name: D:/dyk.log
logging.file.path
不可以指定文件名称, 必须要指定一个物理文件夹路径,会默认使用spring.log
logging:
pattern:
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
path: "./logs/"
file: "./logs/my.log"
file.max-size: 10MB
level:
root: INFO
项⽬中使⽤⽇志
@Controller
public class HelloController {
//声明⽇志成员
private static final Logger log =
LoggerFactory.getLogger(HelloController.class);
@RequestMapping("/hello")
@ResponseBody
public String hello(){
System.out.println("======hello world=======");
logger.debug("DEBUG,{}","信息");
logger.info("INFO,{}","信息");
logger.warn("WARN,{}","信息");
logger.error("ERROR,{}","信息");
return "hello";
}
}
web开发
我们可以把静态资源放到 resources/static (或者resources/public 或者 resources/resources 或者 resources/META-INF/resources ) 中即可
静态资源放完后,
例如我们想访问文件:resources/static/index.html 只需要在访问时资源路径写成/index.html即可。
例如我们想访问文件:resources/static/pages/login.html 访问的资源路径写成: /pages/login.html
修改静态资源访问路径
pringBoot默认的静态资源路径匹配为/** 。如果想要修改可以通过 spring.mvc.static-pathpattern 这个配置进行修改。
例如想让访问静态资源的url必须前缀有/res。例如/res/index.html 才能访问到static目录中的。我们可
以修改如下:
在application.yml中
spring:
mvc:
static-path-pattern: /res/**
此时再访问时就必须要
http://localhost:8080/res/index.html
修改静态资源存放目录
我们可以修改 spring.web.resources.static-locations 这个配置来修改静态资源的存放目录
spring:
web:
resources:
static-locations: classpath:/dykstatic/
如果修改了静态资源存放目录那么原来的static就访问不到了
可以添加一个和static一样的静态资源存放目录
spring:
web:
resources:
static-locations:
- classpath:/dykstatic/
- classpath:/static/
SpringBOOT整合JDBC
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
配置application.yml
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/db3?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
实体类User
@Data
public class User {
private Long id;
private String name;
private double score;
}
UserRepository
public interface UserRepository {
public List<User> findAll();
public User findById(long id);
public void save(User user);
public void update(User user);
public void deleteById(long id);
}
JdbcTemplate主要提供以下几类方法:
execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
query方法及queryForXXX方法:用于执行查询相关语句;
call方法:用于执行存储过程、函数相关语句
UserRepositoryImpl
@Repository
public class UserRepositoryImpl implements UserRepository {
/**
* Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
* JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作
* 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接
*/
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<User> findAll() {
return jdbcTemplate.query("select * from user",new BeanPropertyRowMapper<>(User.class));
}
@Override
public User findById(long id) {
return jdbcTemplate.queryForObject("select * from user where id = ?",new Object[]{id},new BeanPropertyRowMapper<>(User.class));
}
@Override
public void save(User user) {
jdbcTemplate.update("insert into user(name,score) values(?,?)",user.getName(),user.getScore());
}
@Override
public void update(User user) {
jdbcTemplate.update("update user set name = ?,score = ? where id = ?",user.getName(),user.getScore(),user.getId());
}
@Override
public void deleteById(long id) {
jdbcTemplate.update("delete from user where id = ?",id);
}
}
SpringBOOT整合Druid
依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db3?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
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
配置类
package com.blb.springbootmybatis.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.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DruidConfig {
@Bean
// 将所有前缀为spring.datasource下的配置项都加载到DataSource中
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDatasource(){
return new DruidDataSource();
}
//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
// 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
initParams.put("loginPassword", "123456"); //后台管理界面的登录密码
//后台允许谁可以访问
//initParams.put("allow", "localhost"):表示只有本机可以访问
//initParams.put("allow", ""):为空或者为null时,表示允许所有访问
initParams.put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问
//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}
//配置 Druid 监控 之 web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);
//"/*" 表示过滤所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
druid 启动starter
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid‐spring‐boot‐starter</artifactId>
<version>1.2.3</version>
</dependency>
SpringBOOT整合mybatis
导入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
创建数据库
create table district(
id int ,
name varchar(50)
)charset =utf8;
CREATE table air(
id int ,
district_id int references district(id),
monitor_time date,
pm10 int,
pm25 int,
monitoring_station varchar(55),
last_modify_time date
)charset =utf8
实体类
这里教一个偷懒的办法,选中右边的表单机右键
public class District {
private Integer id;
private String name;
get/set
}
public class Air {
private Integer id;
private Integer districtId;
private java.sql.Date monitorTime;
private Integer pm10;
private Integer pm25;
private String monitoringStation;
private java.sql.Date lastModifyTime;
get/set
}
准备接口
AirMapper
package com.blb.springbootmybatis.dao;
import com.blb.springbootmybatis.entity.Air;
import java.util.List;
public interface AirMapper {
List<Air> findAll();
}
在启动文件类中添加直接扫描Mapper接口所在的包
@MapperScan(basePackages = "com.blb.springbootmybatis.mapper")
package com.blb.springbootmybatis;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.blb.springbootmybatis.mapper")
public class SpringbootMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisApplication.class, args);
}
}
准备映射文件
在resources创建一个mapper文件夹在该文件夹下创建一个AirMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blb.springbootmybatis.mapper.AirMapper">
<select id="findAll" resultType="Air">
select * from air
</select>
</mapper>
yml配置
mybatis配置
mybatis:
# 扫描映射文件
mapper-locations: classpath:mapper/*.xml
# 配置别名扫描的包
type-aliases-package: com.blb.springbootmybatis.entity
configuration:
# 开启驼峰映射配置
map-underscore-to-camel-case: true
指定连接数据库的信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db3?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
完整的yml配置
server:
port: 8080
servlet:
context-path: /
#连接数据库的信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db3?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
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
mybatis:
# 扫描映射文件
mapper-locations: classpath:mapper/*.xml
# 配置别名扫描的包
type-aliases-package: com.blb.springbootmybatis.entity
configuration:
# 开启驼峰映射配置
map-underscore-to-camel-case: true
测试
右键点击mapper接口
把默认的测试类作为父类,并加上public修饰符
package com.blb.springbootmybatis;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class SpringbootMybatisApplicationTests {
@Test
void contextLoads() {
}
}
让生成的测试类继承默认的测试类
package com.blb.springbootmybatis.mapper;
import com.blb.springbootmybatis.SpringbootMybatisApplicationTests;
import com.blb.springbootmybatis.entity.Air;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class AirMapperTest extends SpringbootMybatisApplicationTests {
@Autowired
private AirMapper airMapper;
@Test
void findAll() {
List<Air> list= airMapper.findAll();
for (Air air:list){
System.out.println(air);
}
}
}
注解方式整合mybatis
注解方式在编写配置简单,简单sql推荐使用
创建mapper接口
package com.blb.springbootmybatis.mapper;
import com.blb.springbootmybatis.entity.District;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface DistrictMapper {
@Select("select * from district")
List<District> findAll();
@Select("select * from district where id=#{id}")
District findOne(@Param("id") Integer id);
}
添加mybatis注解
针对增删改查:@Insert @Delete @Update @Select
还是需要在启动类中添加@MapperScan注解
package com.blb.springbootmybatis.mapper;
import com.blb.springbootmybatis.entity.District;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface DistrictMapper {
@Select("select * from district")
List<District> findAll();
@Select("select * from district where id=#{id}")
District findOne(@Param("id") Integer id);
}
添加配置
#查看sql语句
logging:
level:
com.blb.springbootmybatis.mapper: debug
测试
package com.blb.springbootmybatis.mapper;
import com.blb.springbootmybatis.SpringbootMybatisApplicationTests;
import com.blb.springbootmybatis.entity.District;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class DistrictMapperTest extends SpringbootMybatisApplicationTests {
@Autowired
private DistrictMapper mapper;
@Test
void findAll() {
List<District> list=mapper.findAll();
for(District district:list){
System.out.println(district);
}
}
@Test
void findOne() {
}
}
SpringBOOT整合分页助手
导入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
测试
public void findByPage(){
//1.执行分页
//第一个参数是第几页,第二个参数是每页几条
PageHelper.startPage(1,5);
//2.执行查询
List<Air> list = airMapper.findAll();
//3.封装PageInfo对象
PageInfo<Air> pageInfo=new PageInfo<>(list);
//输出
for (Air air : pageInfo.getList()) {
System.out.println(air);
}
}
SpringBOOT整合JSP
导入依赖
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
右键点击main创建webapp/WEB-INF文件夹
创建Controller
package com.blb.springbootmybatis.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class JspController {
@GetMapping("/index")
public String index(Model model){
model.addAttribute("name","dyk");
return "index";
}
}
配置前缀和后缀
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db3?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
mvc:
view:
#视图的前缀和后缀
#什么都不写代表着默认去webapp下面找
prefix: /WEB-INF/
suffix: .jsp
自动刷新
server:
port: 8080
servlet:
context-path: /
jsp:
init-parameters:
development: true #开启jsp开发模式每次不用重启
Spring Boot整合Thymeleaf 模版
SpringBoot项目默认不是JSP,那如何来实现我们的动态页面呢?SpringBoot推荐模板引擎Thymeleaf。其实JSP也属于一种模板引擎,比较常用的还有freemaker。模板引擎非常多,道理都一样
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
spirngboot默认配置了Thymeleaf
也可以自己写
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mode: HTML
encoding: UTF-8
如果希望客户端可以直接访问 HTML 资源,将这些资源放置在 static 路径下即可,否则必须通过 Handler 的后台映射才可以访问静态资源。
注意使用时一定别忘了导入命名空间
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>
创建模板文件
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${msg}"></p>
<!-- 方式1 -->
<p th:text="'提示:'+${msg}"></p>
<!-- 方式2 -->
<p> 提示:[[${person.name}]] </p>
</body>
</html>
-
<html xmlns:th="http://www.thymeleaf.org">
是引入thymeleaf的命名空间,必须。 -
可以在原来的任何属性前面加上前缀
th:*
来表示thymeleaf的定义。data-th-*
效果一样。 -
在标签内拼接字符串比较麻烦,可以在里面通过[[]]来显示跟拼接
定义页面
在classpath:templates
路径下创建html文件作为显示页面,一定要放在此路径,并且一定要是html格式,比如hello.html
简单表达式
Thymeleaf 提供了非常丰富的标准表达式语法,总共有 8 大类:
-
简单表达式
-
字面值
-
文本操作
-
算术运算
-
布尔运算
-
比较和相等
-
条件运算
-
无操作符
语法 | 名称 | 描述 | 作用 |
---|---|---|---|
${…} | Variable Expressions | 变量表达式 | 取出上下文变量的值 |
*{…} | Selection Variable Expressions | 选择变量表达式 | 取出选择的对象的属性值 |
#{…} | Message Expressions | 消息表达式 | 使文字消息国际化,I18N |
@{…} | Link URL Expressions | 链接表达式 | 用于表示各种超链接地址 |
~{…} | Fragment Expressions | 片段表达式 | 引用一段公共的代码片段 |
Thymeleaf 常用语法
package com.blb.springbootmybatis.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class ThymeleafController {
@GetMapping("/test1")
public ModelAndView test(){
ModelAndView modelAndView=new ModelAndView();
modelAndView.addObject("name","dyk");
modelAndView.setViewName("test");
return modelAndView;
}
}
定义局部变量
使用th:with
属性可以定义局部变量
<span th:with="myName='dyk'">
<p th:text="${myName}" ></p>
</span>
同时定义多个局部变量时,用英文,
号分隔开
<p th:with="name=${user.name},age={user.age}">
......
</p>
注释
标准注释
- 标准注释会在文件源代码中显示出来。
<!-- --->
析器级注释
-
注释的代码块会在引擎解析的时候抹去。
<!--/* ... */-->
取值
和原来jsp的el表达式类似
<!--th:text就是将div中的内容设置为它指定的值,和Vue有点像
一样-->
<div th:text="${name}"></div>
拼接
<div th:text="'我的名字是'+${name}+'年龄是'+18"></div>
单引号加+号拼接
或者直接双引号里面使用| 拼接的内容 |
<div th:text="|我叫${name}|"></div>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:text="${name}"></div>
<div th:text="'我的名字是'+${name}+'年龄是'+18"></div>
<div th:text="|我叫${name}|"></div>
</body>
</html>
*{…}
变量表达式${}
是面向整个上下文的,而选择变量表达式*{}
的上下文是父标签(th:object
)所选择的对象。
// Person bean
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Person {
private String name ;
private String sex ;
private Integer age ;
}
// controller
@RequestMapping("/hello")
public String welcome(Model model){
Person person = new Person("dyk","男",18);
model.addAttribute("person",person);
return "hello" ;
}
<span th:object="${person}">
<p th:text="*{name}"></p>
<p th:text="*{sex}"></p>
<p th:text="*{age}"></p>
</span>
<!-- 跟下面效果等效 -->
<span >
<p th:text="${person.name}"></p>
<p th:text="${person.sex}"></p>
<p th:text="${person.age}"></p>
</span>
内联表达式
内联表达式允许我们直接在 HTML 文本中使用标准表达式,而不需要使用th:*
标签属性
[[ ]]
[[]]
相当于th:text
,对含有 HTML 标签的内容自动进行字符转义
<p>[[${msg}]]</p>
[( )]
[()]
相当于th:utext
,对含有 HTML 标签的内容不进行字符转义
<p>[(${msg})]</p>
th:inline
我们已经了解到,使用[[]]
和[()]
语法可以直接在 HTML 文本中使用标准表达式,如果想要使用更多高级的功能,需要使用th:inline
属性来激活,它的取值如下
值 | 描述 |
---|---|
none | 禁止内联表达式,可以原样输出 [[]] 和 [()] 字符串 |
text | 文本内联,可以使用 th:each 等高级语法 |
css | 样式内联,如<style th:inline="css"> |
javascript | 脚本内联,如:<style th:inline="javascript"> |
-
none
<!-- [[ ]]不会被解析,原字符串输出 --> <p th:inline="none">[[asdfsafd]]</p>
-
text
<p th:inline="text">[[${msg}]]</p>
-
css
<style th:inline="css"> body { background-color:[[${bgColor}]]; } </style>
字面值
所谓字面值,首先它不是一个变量,它是一个具体的确切的值,通常这些值是比较简单的,例如:18
、'welcome'
等,它们没有名称,以至于我们只能用值来称呼它们,因此我们称其为字面值
文字字面值
- 文字字面值是用单引号引起来的任何字符内容,如果字符内容里面含有单引号,则需要进行转义
<p th:text="'Welcome to bailiban...'"></p>
数字字面值
<!-- 2019 -->
<p th:text="2019"></p>
<!-- 2020 -->
<p th:text="2019 + 1"></p>
布尔字面值
<!-- false -->
<p th:text="true"></p>
<!-- false -->
<p th:text="1 > 2"></p>
<!-- 否 -->
<p th:text="1 > 2 ? '是' : '否'"></p>
空字面值
<!-- false -->
<p th:text="${user == null}"></p>
字符串连接
不管是字面值还是表达式的结果,我们都可以使用+
符号将它们连接起来:
<p th:text="'Welcome to ' + ${msg} + '!'"></p>
字面值替换
符号||
可以用来将字面值和表达式包裹起来,这样就能方便的替换变量的值,而不需要使用+
连接符:
<p th:text="|Welcome to ${msg} !|"></p>
算术运算
支持+
(加)、-
(减)、*
(乘)、/
(除)、%
(模)运算。
<!-- 6 -->
<p th:text="4 + 2"></p>
<!-- 2 -->
<p th:text="4 - 2"></p>
<!-- 8 -->
<p th:text="4 * 2"></p>
<!-- 2 -->
<p th:text="4 / 2"></p>
<!-- 0 -->
<p th:text="4 % 2"></p>
<!-- 2 -->
<p th:text="${page + 1}"></p>
<!-- 2 -->
<p th:text="${page} + 1"></p>
布尔运算
支持and
(且)、or
(或)、!
(非)、not
(非)运算
<!--true-->
<p th:text="${true and true}"></p>
<!--true-->
<p th:text="${true or false}"></p>
<!--false-->
<p th:text="${!true}"></p>
<!--true-->
<p th:text="${not false}"></p>
比较运算
支持<
(lt
)、>
(gt
)、<=
(le
)、>=
(ge
)、==
(eq
)、!=
(ne
)
<p th:text="${person.age < 60}"></p>
<p th:text="${person.age <= 60}"></p>
<p th:text="${person.age > 18}"></p>
<p th:text="${person.age >= 18}"></p>
<p th:text="${person.age == 18}"></p>
<p th:text="${person.age != 18}"></p>
- gt great than 大于
- ge great equal 大于等于
- eq equal 等于
- lt less than 小于
- le less equal 小于等于
- ne not equal 不等于
三元运算
(if) ? (then) : (else)
<input th:value="${name == 'dyk'? '是':'不是'}"/>
<div th:text="${age >= 20? '青年':'少年'}"></div>
<div th:text="${age ge 20? '青年':'少年'}"></div>
<p >[[${person.age >= 18?'成年':'未成年'}]]</p>
二元运算符
(value) ?: (defaultValue)
其中,value
非空(null)即真,条件为真时输出value
,否则输出defaultValue
。
<p th:text="${person.name}?:'用户名不能为空'"></p>
也可以使用无操作符达到相同效果。_
表示显示标签体内的内容。
<p th:text="${person.name}?:_">用户名不能为空</p>
使用文本
th:text
在标签体中展示表达式评估结果的文本内容
<p th:text="${message}">Welcome to bailiban!</p>
当它作为静态文件直接运行时,浏览器会自动忽略它不能识别的th:text
属性,而显示标签体的文本内容Welcome to bailiban!
当它作为模板文件运行在服务器端时,th:text
属性的具体值将会替换标签体的文本内容
##### th:utext
utext
跟text
的区别是utext会解析html标签,而text
不会。
假设model.addAttribute("msg","<b>welcome to bailiban ...</b> ");
<!--<b>welcome to bailiban ...</b> -->
<p th:text="${msg}"></p>
<!-- 显示welcome to bailiban ...,字体变粗,解析b标签的结果-->
<p th:utext="${msg}"></p>
设置属性
在 Thymeleaf 模板文件中,你可以使用th:*
(或者使用th:attr
属性)来设置任意的 HTML5 标签属性的值。不仅如此,你还可以th:*-*
来同时为多个不同的标签属性设置相同的一个值,甚至你可以使用th:attrappend
和th:attrprepend
来追加新的值到现有的标签属性值中
th:attr
这种方式是不被推荐的,了解一下就行。下面是用th:attr="href=..."
来设置标签href
属性的值
<a th:attr="href=@{https://www.google.com}">谷歌一下你就知道</a>
th:*
th:attr
不够简洁,我们推荐下面的方式
<a th:href="@{https://www.google.com}">谷歌一下你就知道</a>
其中的*
可以是HTML5中的任意属性
th:*-*
如果想要同时为标签的多个不同属性设置相同的一个值,可以使用th:*-*
的语法
<img src="logo.png" th:alt-title="LOGO图片">
它相当于:
<img src="logo.png" th:alt="LOGO图片" th:title="LOGO图片">
th:attrappend & th:attrprepend
<!-- <button class="btn disable ">购买</button> -->
<button class="btn" th:attrappend="class=' disable '">购买</button>
<!-- <button class=" disable btn">购买</button> -->
<button class="btn" th:attrprepend="class=' disable '">购买</button>
布尔属性
在 HTML 中有些属性是布尔属性,布尔属性是指没有值的属性,如readonly
、checked
、selected
等。它们若存在那就意味着值为 true。 Thymeleaf 也允许我们通过th:*
(这里的*
表示任意的布尔属性) 来选择是否使用这些布尔属性
<input type="checkbox" th:checked="${remember}" /> 记住我
当${remember}
为null就不会默认选中,不为null则会默认勾选上
条件判断
th:if
th:if 表示条件成立时显示内容
当表达式的评估结果为真时则显示内容,否则不显示
<div th:if="${name=='dyk'}" th:text="${name}"></div>
<div th:if="${name=='dyk'}" >[[${name}]]</div>
取值也可以不写在标签里面,通过两个中括号
th:unless
th:unless 表示条件不成立时显示内容
th:unless
与th:if
判断恰好相反,当表达式的评估结果为假时则显示内容,否则不显示
<div th:unless="${name=='cb'}" th:text="${name}"></div>
<div th:unless="${name=='cb'}">[[${name}]]</div>
由于我存的name值是dyk不等于cb所以会取出来
th:swith
多路选择语句,它需要搭配th:case
来使用:
<div th:switch="${person.sex}">
<p th:case="男">man</p>
<p th:case="女">girl</p>
</div>
循环遍历
遍历(迭代)的语法th:each="自定义的元素变量名称 : ${集合变量名称}"
<table border="1px solid #000">
<tr>
<th>id</th>
<th>username</th>
<th>password</th>
<th>hobby</th>
<th>email</th>
<th>操作</th>
</tr>
<tr th:each="user:${users}">
<td th:text="${user.id}"></td>
<td th:text="${user.username}"></td>
<td th:text="${user.password}"></td>
<td th:text="${user.hobby}"></td>
<td th:text="${user.email}"></td>
</tr>
</th>
</table>
- 属性
th:each
提供了一个用于跟踪迭代的状态变量,stat 状态变量的使用语法:th:each="自定义的元素变量名称, 自定义的状态变量名称 : ${集合变量名称}"
。 它包含以下几个属性 :
属性 | 类型 | 描述 |
---|---|---|
index | int | 当前迭代的索引,从 0 开始 |
count | int | 当前迭代的计数,从 1 开始 |
size | int | 集合中元素的总个数 |
current | int | 当前的元素对象 |
even | boolean | 当前迭代的计数是否是偶数 |
odd | boolean | 当前迭代的计数是否是奇数 |
first | boolean | 当前元素是否是集合的第一个元素 |
last | boolean | 当前元素是否是集合的最后一个元素 |
各行变色表格
<table border="1px solid #000">
<tr>
<th>id</th>
<th>username</th>
<th>password</th>
<th>hobby</th>
<th>email</th>
<th>操作</th>
</tr>
<tr th:each="user,stat:${users}" th:style="'background-color:'+@{${stat.odd}?'#F2F2F2'}">
<td th:text="${user.id}"></td>
<td th:text="${user.username}"></td>
<td th:text="${user.password}"></td>
<td th:text="${user.hobby}"></td>
<td th:text="${user.email}"></td>
<td>
<a href="/toAddPage">添加</a>
<a href="/delete/${user.id}">删除</a>
<a href="/toUpdatePage/${user.id}">修改</a>
</td>
</tr>
</th>
</table>
URL @{…} 链表表达式
Thymeleaf 对于 URL 的处理是通过 @{...}
进行处理,结合 th:href 、th:src
绝对地址
<a th:href="@{http://www.baidu.com}" th:text="baidu"></a>
相对地址
<!-- 不带参数 -->
<a th:href="@{hello2}" th:text="hello2"></a> <br/>
<!-- 带参数 -->
<a th:href="@{hello2(name=${person.name},password='123')}" th:text="hello2"></a> <br/>
- 页面跳转必须通过后台controller
- 参数都放在
()
括号内,参数之间用,
号隔开 - 参数值变量通过
${}
引用
<a th:href="@{http://www.baidu.com}">跳转</a>
<a th:href="@{http://localhost:9090/index/url/{na}(na=${name})}">跳转2</a>
<img th:src="${src}">
<div th:style="'background:url('+ @{${src}} +');'">
<br/>
<br/>
<br/>
</div>
复用代码片段~{…}
~{…}片段表达式主要用来引用一段可重复使用的代码片段
- base/common.html
<div th:fragment="foot-copy" >
Copyright © 2012-2019 管理系统 - Version V1.1
</div>
- hello.html
<div th:insert="~{base/common::foot-copy}"></div>
-
base/common
是文件路径,foot-copy
是定义的th:fragment
值,用::
分割。
-
~{}
可以省略。
除了使用th:fragment
还可以通过选择器来引用。 -
base/common.html
<div class="foot-copy" >
Copyright © 2012-2019 管理系统 - Version V1.1
</div>
- hello.html
<div th:insert="~{base/common::.foot-copy}"></div>
.foot-copy
是CSS选择器去匹配。
我们定义代码片段时还可以声明一组参数:
- base/common.html
<div th:fragment="menu(menuName1, menuName2)">
<p th:text="${menuName1}"></p>
<p th:text="${menuName2}"></p>
</div>
- hello.html
<div th:insert="~{base/common::menu('用户中心', '我的订单')}"></div>
th:insert
跟th:replace
的区别?
他们都是用来使用代码片段, th:insert 如同插入的字面意思,将指定的代码片段插入主标签内 。 th:replace 如同替换的字面意思,将主标签替换为指定的代码片段。
switch
@GetMapping("/switch")
public String switchTest(Model model){
model.addAttribute("gender","女");
return "test";
}
<div th:switch="${gender}">
<p th:case="女">女</p>
<p th:case="男">男</p>
<p th:case="*">未知</p>
</div>
基本对象
对象 | 描述 |
---|---|
#ctx | 上下文对象 |
#vars | 同 #ctx,表示上下文变量 |
#locale | 上下文本地化(特定的地理区域)变量,可参考 java.util.Locale |
#request | HttpServletRequest 对象,可参考 javax.servlet.http.HttpServletRequest |
#response | HttpServletResponse 对象,可参考 javax.servlet.http.HttpServletResponse |
#session | HttpSession 对象,可参考 javax.servlet.http.HttpSession |
#servletContext | ServletContext 对象,可参考 javax.servlet.ServletContext |
#request
<!-- HTTP/1.1 -->
<p th:text="${#request.protocol}"></p>
<!-- http -->
<p th:text="${#request.scheme}"></p>
<!-- localhost -->
<p th:text="${#request.serverName}"></p>
<!-- 8080 -->
<p th:text="${#request.serverPort}"></p>
<!-- GET -->
<p th:text="${#request.method}"></p>
<!-- /hello -->
<p th:text="${#request.requestURI}"></p>
<!-- http://localhost:8080/hello -->
<p th:text="${#request.requestURL}"></p>
<!-- /hello -->
<p th:text="${#request.servletPath}"></p>
<!-- zhangsan -->
<p th:text="${#request.getAttribute('student').name}"></p>
<!-- name=zhang -->
<p th:text="${#request.queryString}"></p>
#response
<!-- 200 -->
<p th:text="${#response.status}"></p>
<!-- 8192 -->
<p th:text="${#response.bufferSize}"></p>
<!-- UTF-8 -->
<p th:text="${#response.characterEncoding}"></p>
<!-- text/html;charset=UTF-8 -->
<p th:text="${#response.contentType}"></p>
#session
<!-- 需要在后台开启会话,存入person数据 -->
<!-- BFE8F9E54C863E128B107532A902E921 -->
<p th:text="${#session.id}"></p>
<!-- 1499786693244 -->
<p th:text="${#session.lastAccessedTime}"></p>
<!-- seven -->
<p th:text="${#session.getAttribute('person').name}"></p>
@GetMapping("/object")
public String object(HttpServletRequest request){
request.setAttribute("request","request对象");
request.getSession().setAttribute("session","session对象");
return "test";
}
<p th:text="${#request.getAttribute('request')}"></p>
<p th:text="${#session.getAttribute('session')}"></p>
<p th:text="${#locale.country}"></p>
内嵌对象
可以直接通过 # 访问。
对象 | 作用 |
---|---|
dates | java.util.Date 的功能方法 |
calendars | java.util.Calendar 的功能方法 |
numbers | 格式化数字 |
strings | java.lang.String 的功能方法 |
objects | Object 的功能方法 |
bools | 对布尔求值的方法 |
arrays | 操作数组的功能方法 |
lists | 操作集合的功能方法 |
sets | 操作集合的功能方法 |
maps | 操作集合的功能方法 |
@GetMapping("/util")
public String util(Model model){
model.addAttribute("name","zhangsan");
model.addAttribute("users",new ArrayList<>());
model.addAttribute("count",22);
model.addAttribute("date",new Date());
return "test";
}
<!-- 格式化时间 -->
<p th:text="${#dates.format(date,'yyyy-MM-dd HH:mm:sss')}"></p>
<!-- 创建当前时间,精确到天 -->
<p th:text="${#dates.createToday()}"></p>
<!-- 创建当前时间,精确到秒 -->
<p th:text="${#dates.createNow()}"></p>
<!-- 判断是否为空 -->
<p th:text="${#strings.isEmpty(name)}"></p>
<!-- 判断List是否为空 -->
<p th:text="${#lists.isEmpty(users)}"></p>
<!-- 输出字符串长度 -->
<p th:text="${#strings.length(name)}"></p>
<!-- 拼接字符串 -->
<p th:text="${#strings.concat(name,name,name)}"></p>
<!-- 创建自定义字符串 -->
<p th:text="${#strings.randomAlphanumeric(count)}"></p>
AOP面向切面编程
回顾aop
- AOP :Aspect(切面) Oriented(面向)Programmaing(编程)
- Aspect(切面)=Advice(通知)+切入点(Pointcut)
- Advice 通知:业务逻辑中一些附加操作称之为通知 ,前置,后置, 环绕
- Pointcut 切入点: 配置通知应用项目中那些业务操作
- Aspect 切面=附加操作(Advice)+切入点(Pointcut)
springboot是对原有项⽬中spring框架和springmvc的进⼀步封装,因此在springboot中同样⽀持spring框架中AOP切⾯编程,不过在springboot中为了快速开发仅仅提供了注解⽅式的切⾯编程
aop依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
切入点表达式
execution 方法级别切入点表达式,运行效率低
within:类级别的切入点表达式
@Before("execution(* com.blb.service.*.*(..))")
@Before("within(com.blb.service.*)")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAdvice {
}
基于注解的切入点表达式
@annotation(com.blb.annotations.xxx)
想要执行切入操作只需要在方法上加入自定义注解即可
注解
- @Aspect ⽤来类上,代表这个类是⼀个切⾯
- @Before ⽤在⽅法上代表这个⽅法是⼀个前置通知⽅法
- @After ⽤在⽅法上代表这个⽅法是⼀个后置通知⽅法 @Around ⽤在⽅法上代表这个⽅法
是⼀个环绕的⽅法
- @Around ⽤在⽅法上代表这个⽅法是⼀个环绕的⽅法
@Before
@Aspect
@Configuration
public class MyAspect {
@Before("execution(* com.blb.service.*.*(..))")
public void before(JoinPoint joinPoint){
System.out.println("前置通知");
joinPoint.getTarget();//⽬标对象
joinPoint.getSignature();//⽅法签名
joinPoint.getArgs();//⽅法参数
}
}
@After
@Aspect
@Configuration
public class MyAspect {
@After("execution(* com.blb.service.*.*(..))")
public void before(JoinPoint joinPoint){
System.out.println("后置通知");
joinPoint.getTarget();//⽬标对象
joinPoint.getSignature();//⽅法签名
joinPoint.getArgs();//⽅法参数
}
}
注意: 前置通知和后置通知都没有返回值,⽅法参数都为joinpoint joinpoin
@Around
@Aspect
@Configuration
public class MyAspect {
@Around("execution(* com.baizhi.service.*.*(..))")
public Object before(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
System.out.println("进⼊环绕通知");
proceedingJoinPoint.getTarget();//⽬标对象
proceedingJoinPoint.getSignature();//⽅法签名
proceedingJoinPoint.getArgs();//⽅法参数
Object proceed = proceedingJoinPoint.proceed();//放⾏执⾏⽬标⽅法
System.out.println("⽬标⽅法执⾏之后回到环绕通知");
return proceed;//返回⽬标⽅法返回值
}
}
注意: 环绕通知存在返回值,参数为ProceedingJoinPoint, ProceedingJoinPoint,如果执⾏放⾏,不会执⾏⽬标⽅法,⼀旦放⾏必须将⽬标⽅法的返回值返回,否则调⽤者⽆法接受返回数据
部署
war包部署
修改项目pom.xml
<packaging>war</packaging>
排除springboot内嵌tomcat依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope> <!--去掉内嵌tomcat-->
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope> <!--去掉使⽤内嵌tomcat解析jsp-->
</dependency>
在插件中指定⼊⼝类
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<!--增加jvm参数-->
<jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
<!--指定启动⼊⼝类-->
<mainClass>com.blb.emp.EmpApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
配置启动类
package com.blb.emp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
@MapperScan("com.blb.emp.mapper")
//1.继承SpringBootServletInitializer
//2.覆盖configure⽅法
public class EmpApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(EmpApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(EmpApplication.class);
}
}
打包测试
# ⼀旦使⽤war包部署注意:
- 1. application.yml 中配置port context-path 失效
- 2. 访问时使⽤打成war包的名字和外部tomcat端⼝号进⾏访问项⽬
jar包部署
默认就是jar
<packaging>jar</packaging>
启动jar包
java -jar 对应jar文件名字 nohup &
如果是thymeleaf或者前后端的springboot项目这样已经部署成功了并且是后台运行
但是一般还是会写成这样部署
nohup java -jar springbootHelloWorld.jar --server.port=8082 > server.log 2&>1 &
nohup命令
nohup命令用于不挂断地运行命令(关闭当前session不会中断改程序,只能通过kill等命令删除)。
使用nohup命令提交作业,如果使用nohup命令提交作业,那么在缺省情况下该作业的所有输出都被重定向到一个名为nohup.out的文件中,除非另外指定了输出文件。
&
&用于后台执行程序,但是关闭当前session程序也会结束
2>&1 &
bash中:
-
0 代表STDIN_FILENO 标准输入(一般是键盘),
-
1 代表STDOUT_FILENO 标准输出(一般是显示屏,准确的说是用户终端控制台),
-
2 代表STDERR_FILENO (标准错误(出错信息输出)。
-
>直接把内容生成到指定文件,会覆盖原来文件中的内容
-
>> 尾部追加,不会覆盖原有内容
-
< 将指定文件的内容作为前面命令的参数
2>&1就是用来将标准错误2重定向到标准输出1中的。此处1前面的&就是为了让bash将1解释成标准输出而不是文件1。至于最后一个&,则是让bash在后台执行。
jsp的springboot项目部署
因为springboot默认不支持jsp如果用上面直接部署会访问不到404
修改插件版本
<plugins>
<!--版本必须为1.4.2版本-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.4.2.RELEASE</version>
</plugin>
</plugins>
注意:springboot :springboot部署jsp时,插件版本必须指定为1.4.2版本,并进⾏jsp
打包配置才可以,其他版本均不⽀持!!!
指定jsp打包配置
<resources>
<!-- 打包时将jsp⽂件拷⻉到META-INF⽬录下-->
<resource>
<!-- 指定resources插件处理哪个⽬录下的资源⽂件 -->
<directory>src/main/webapp</directory>
<!--指定必须要放在此⽬录下才能被访问到-->
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/**</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/**</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
完整版
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.4.2.RELEASE</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
<resources>
<!-- 打包时将jsp⽂件拷⻉到META-INF⽬录下-->
<resource>
<!-- 指定resources插件处理哪个⽬录下的资源⽂件 -->
<directory>src/main/webapp/WEB-INF</directory>
<!--指定必须要放在此⽬录下才能被访问到-->
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/**</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/**</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
⽂件上传
⽤户访问当前系统,将⾃⼰本地计算机中⽂件通过浏览器上传到当前系统所在的服务器过程中称之为⽂件的上传
上传页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<h1>文件上传</h1>
<form action="/file/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file"> <br>
<input type="submit" value="上传文件">
</form>
</body>
</html>
注意页面要注意一下几点
- 表单提交⽅式必须是post
- 表单的enctype属性必须为multipart/form-data
- 后台接受变量名字要与⽂件选择name属性⼀致
控制器
package com.blb.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@Controller
@RequestMapping("/file")
public class FileController {
private static final Logger log = LoggerFactory.getLogger(FileController.class);
@RequestMapping("/toUploadPage")
public String toUploadPage(){
return "upload";
}
@RequestMapping("/upload")
public String upload(MultipartFile file, HttpServletRequest request) {
//接收文件的对象MultipartFile 变量名要和表单的 input type=file标签的 name属性的名字一样
log.debug("文件名:{}",file.getOriginalFilename());
log.debug("文件大小:{}",file.getSize());
log.debug("文件类型:{}",file.getContentType());
String fileName=file.getOriginalFilename();//获取文件名以及后缀
fileName= UUID.randomUUID()+"_"+fileName;//重新生成文件夹名
//指定上传文件的路径存储,这里是静态资源static的upload
String path=System.getProperty("user.dir");
String dirPath=path+"/src/main/resources/static/upload";
File filePath=new File(dirPath);
if(!filePath.exists()){
filePath.mkdirs();
}
try {
//2.上传文件 参数:将文件写入到那个目录
file.transferTo(new File(dirPath+fileName));
} catch (IOException e) {
e.printStackTrace();
}
return "upload";
}
}
文件yml配置修改文件上传大小
server:
port: 8080
servlet:
context-path: /
spring:
thymeleaf:
cache: false
servlet:
multipart: #修改文件上传的大小限制
max-request-size: 10MB #允许请求传递文件大小最大为10M
max-file-size: 10MB #允许服务器可以处理的最大文件大小
logging:
level:
root: info
com.blb: debug
如果部署的话这样指定文件路径就不是很好,建议把上传路径放到配置文件上面去,通过配置文件注入
package com.blb.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@Controller
@RequestMapping("/file")
public class FileController {
private static final Logger log = LoggerFactory.getLogger(FileController.class);
@Value("${file.upload.path}")
private String uploadPath;
@RequestMapping("/toUploadPage")
public String toUploadPage(){
return "upload";
}
@RequestMapping("/upload")
public String upload(MultipartFile file, HttpServletRequest request) {
//接收文件的对象MultipartFile 变量名要和表单的 input type=file标签的 name属性的名字一样
log.debug("文件名:{}",file.getOriginalFilename());
log.debug("文件大小:{}",file.getSize());
log.debug("文件类型:{}",file.getContentType());
String fileName=file.getOriginalFilename();//获取文件名以及后缀
fileName= UUID.randomUUID()+"_"+fileName;//重新生成文件夹名
//指定上传文件的路径存储,这里是静态资源static的upload
File filePath=new File(uploadPath);
if(!filePath.exists()){
filePath.mkdirs();
}
try {
//2.上传文件 参数:将文件写入到那个目录
file.transferTo(new File(uploadPath,fileName));
} catch (IOException e) {
e.printStackTrace();
}
return "upload";
}
}
配置文件
server:
port: 8083
servlet:
context-path: /
spring:
thymeleaf:
cache: false
servlet:
multipart: #修改文件上传的大小限制
max-request-size: 10MB #允许请求传递文件大小最大为10M
max-file-size: 10MB #允许服务器可以处理的最大文件大小
logging:
level:
root: info
com.blb: debug
#指定文件上传位置
file:
upload:
path: /home/dyk/upload
文件下载
⽂件下载: 将服务器某个资源⽂件下载到⽤户本地计算机过程称之为⽂件下载
下载页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<h1>文件上传</h1>
<form action="/file/upload2" method="post" enctype="multipart/form-data">
<input type="file" name="file"> <br>
<input type="submit" value="上传文件">
</form>
</body>
</html>
控制器
package com.blb.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.UUID;
@Controller
@RequestMapping("/file")
public class FileController {
private static final Logger log = LoggerFactory.getLogger(FileController.class);
@RequestMapping("/download")
public void download(String filename, HttpServletResponse response) throws IOException {
log.debug("当前下载的问价名:{}",filename);
String path=System.getProperty("user.dir");
String dirPath=path+"/src/main/resources/static/upload";
File filePath=new File(dirPath,filename);
//将文件读取为输入流
FileInputStream inputStream=new FileInputStream(filePath);
//获取响应流之前,一定要以附件形式下载,attachment附件
response.setHeader("content-disposition","attachment;filename="+ URLEncoder.encode(filename,"UTF-8"));
//获取响应输出流
ServletOutputStream outputStream = response.getOutputStream();
// 输入流复制给输出流
int len=0;
byte[] b=new byte[1024];
while (true){
len=inputStream.read(b);
if(len==-1){
break;
}
outputStream.write(b,0,len);
}
//释放资源
inputStream.close();
}
}
优化
package com.blb.controller;
import ch.qos.logback.core.util.FileUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.UUID;
@Controller
@RequestMapping("/file")
public class FileController {
private static final Logger log = LoggerFactory.getLogger(FileController.class);
@Value("${file.download.path}")
private String downloadPath;
@RequestMapping("/toUploadPage")
public String toUploadPage(){
return "upload";
}
@RequestMapping("/todownloadPage")
public String todownloadPage(){
return "download";
}
@RequestMapping("/download")
public void download(String filename, HttpServletResponse response) throws IOException {
log.debug("当前下载的问价名:{}",filename);
//String path=System.getProperty("user.dir");
//String dirPath=path+"/src/main/resources/static/upload";
File filePath=new File(downloadPath,filename);
//将文件读取为输入流
FileInputStream inputStream=new FileInputStream(filePath);
//获取响应流之前,一定要以附件形式下载,attachment附件
response.setHeader("content-disposition","attachment;filename="+ URLEncoder.encode(filename,"UTF-8"));
//获取响应输出流
ServletOutputStream outputStream = response.getOutputStream();
// 输入流复制给输出流
FileCopyUtils.copy(inputStream,outputStream);
//释放资源
}
}
配置文件
server:
port: 8083
servlet:
context-path: /
spring:
thymeleaf:
cache: false
servlet:
multipart: #修改文件上传的大小限制
max-request-size: 10MB #允许请求传递文件大小最大为10M
max-file-size: 10MB #允许服务器可以处理的最大文件大小
logging:
level:
root: info
com.blb: debug
#指定文件上传位置
file:
upload:
path: /home/dyk/upload
download:
path: /home/dyk/upload
拦截器 interceptor
filter过滤器:过滤可以拦截javaweb中请求,放行,中断
拦截器类似于javaweb中的过滤器功能,但是只能拦截controllr相关的请求
作用:将controller中共有的代码放入拦截器中执行,减少controller中代码冗余,其实就是aop的实现
拦截器特性:
1.拦截器只能拦截controller先关请求
2.拦截器可以中断请求轨迹
3.请求之前如果该请求配置了拦截器,请求会先经过拦截器,拦截器放行之后才会执行controller,controller执行完成之后会回到拦截器继续执行拦截器中的代码
拦截器的方法
类 implements HandlerInterceptor 接口 引入默认的实现
preHandler 预处理方法: 最先执行 返回值布尔类型 true放行请求,false中断
postHandler 过程中处理 controller返回之后会执行该方法,执行完成之后会响应浏览器
afterCompletion 最后完成,当响应结束之后会执行拦截器中的这个方法
自定义拦截器
package com.blb.intereptors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义拦截器
*/
public class MyInterceptor1 implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(MyInterceptor1.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("=============1.preHandle==============");//可以根据逻辑自行替换
return true;//放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.debug("=============2.postHandle==============");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.debug("=============3.afterCompletion==============");
}
}
配置拦截器
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**
* 配置拦截器相关方法
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor1())//指定拦截器
.addPathPatterns("/**")//拦截所有
.excludePathPatterns("/file/**");//排除那些路径
}
}
controller
@Controller
@RequestMapping("/test")
public class InterceptorController {
private static final Logger log = LoggerFactory.getLogger(InterceptorController.class);
@RequestMapping("/inter")
public String inter(){
log.debug("===========controller=================");
return "test";
}
}
运行结果顺序
可以看到先执行了preHandle,因为返回来true放行了,再执行了controller,接着执行了postHandle,最后执行了afterCompletion
当有多个拦截器执行时
MyInterceptor2
public class MyInterceptor2 implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(MyInterceptor2.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("=============3.preHandle==============");//可以根据逻辑自行替换
return true;//放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.debug("=============4.postHandle==============");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.debug("=============5.afterCompletion==============");
}
}
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**
* 配置拦截器相关方法
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor1())//指定拦截器
.addPathPatterns("/**")//拦截所有
.excludePathPatterns("/file/**");//排除那些路径
registry.addInterceptor(new MyInterceptor2())//指定拦截器
.addPathPatterns("/**")//拦截所有
.excludePathPatterns("/file/**");//排除那些路径
}
}
运行结果
顺序order
如果想改变顺序除了可以把注册时的顺序变一下还可以通过order
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor("拦截器")
.addPathPatterns("拦截路径")
.excludePathPatterns("排除路径")
.order("指定执⾏顺序")
}
}
注意:order⽤来执⾏多个拦截器的执⾏顺序,order书写是⾃然数,按照⾃然数顺序执⾏
统一异常处理
自定义异常UserNameNotFoundException
package com.blb.emp.execption;
//自定义用户名不存在异常
public class UserNameNotFoundException extends RuntimeException{
public UserNameNotFoundException(String message) {
super(message);
}
}
传统开发的异常处理
package com.blb.emp.execption;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyExceptionResolver implements HandlerExceptionResolver {
//resolveException 当控制器中任意一个方法出现异常时,如果该控制器的方法没有自己的异常处理(try...catch)则会进入当前方法
//注意在异常处理这个方法中,完成自定义异常处理
//参数1: 当前请求对象
//参数2: 当前请求的响应对象
//参数3: 当前出现异常的控制器对象
//参数4: 出现异常的异常对象
//返回值: ModelAndView模型视图
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
//针对不同的异常类型跳转到不同的页面
if(e instanceof UserNameNotFoundException){
modelAndView.setViewName("error");
return modelAndView;
}
modelAndView.setViewName("500");
return modelAndView;
}
}
RestFul的异常处理(前后端分离)
package com.blb.emp.execption;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class MyRestExceptionResolver {
@ExceptionHandler(value = Exception.class) //用在方法上面,作用处理指定异常 value属性用来指定异常类型
@ResponseBody
public ResponseEntity exceptionHandlr(Exception ex){
//或者再用exception判断
// if(ex instanceof UserNameNotFoundException){
// //如果是UserNameNotFoundException执行操作
// }
return new ResponseEntity(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
//处理指定异常
@ExceptionHandler(value = UserNameNotFoundException.class)
@ResponseBody
public ResponseEntity UserNameNotFoundExceptionHandler(Exception e){
return new ResponseEntity(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
CORS 跨域
CORS是⼀个W3C标准,全称是"跨域资源共享"(Cross-origin resourcesharing) 。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从⽽克服了AJAX只能同源使⽤的限制。
同源策略
同源策略 [same origin policy]是浏览器的⼀个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对⽅资源。 同源策略是浏览器安全的基⽯。
源
源 [origin]就是协议、域名和端⼝号
若地址⾥⾯的 协议、域名和端⼝号均相同 则属于同源
# 同源举例
- 例如判断下⾯的URL是否与 http://www.a.com/test/index.html 同源
http://www.a.com/dir/page.html --------->同源
http://www.child.a.com/test/index.html ->不同源,域名不相同
https://www.a.com/test/index.html ------>不同源,协议不相同
http://www.a.com:8080/test/index.html -->不同源,端⼝号不相同
哪些操作不受同源限制
⻚⾯中的链接,重定向以及表单提交是不会受到同源策略限制的;跨域资源的引⼊是可以的。如嵌⼊到⻚⾯中的 < script src="…">
< /script> , < img> , < link> , < iframe> 等
哪些操作受到同源限制
在浏览器中发起⼀个AJAX请求,会受到同源策略限制。
出现错误:Access-Control-Allow-Origin :Access-Control-Allow-Origin
使⽤CORS解决同源限制
@CrossOrigin注解
@RestController
@RequestMapping("demos")
@CrossOrigin
public class DemoController {
@GetMapping
public String demos() {
System.out.println("========demo=======");
return "demo ok";
}
}
全局解决跨域问题
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); // 1允许任何域名使⽤
corsConfiguration.addAllowedHeader("*"); // 2允许任何头
corsConfiguration.addAllowedMethod("*"); // 3允许任何⽅(post、get等)
source.registerCorsConfiguration("/**",corsConfiguration);//4处理所有请求的跨域配置
return new CorsFilter(source);
}
}
日志
日志作用
编译期调试代码
运营期记录信息
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)