Springboot

Spring Boot 是一个快速开发框架,可以迅速搭建出一套基于Spring框架体系的应用,是Spring Cloud的基础。

  • Spring Boot开启了各种自动装配,从而简化代码开发,不需要编写各种配置文件,只需要引入相关依赖就可以迅速搭建一个应用
  • 特点

1、不需要web.xml

2、不需要springmvc.xml

3、不需要tomcat,SpringBoot内嵌了一个tomcat

4、不需要配置JSON解析,支持REST架构

5、个性化配置非常简单

  • 如何使用

Spring Boot 2.x要求必须基于Spring 5.x,要求java版本必须是8以上

Spring Boot的使用

第一种方法是新建Maven工程然后在pom.xml中继承父项目

导入启动器Spring Boot的启动器实际上就是一个依赖。这个依赖中包含了整个这个技术的相关jar包,还包含了这个技术的自动配置,以前绝大多数XML配置都不需要配置了。当然了,启动器中自动配置无法实现所有内容的自动配置,在使用Spring Boot时还需要进行少量的配置(这个配置不是在xml中了,而是在properties或yml中即可)。如果是Spring自己封装的启动器的artifact id名字满足:spring-boot-starter-xxxx,如果是第三方公司提供的启动满足:xxxx-spring-boot-starter。以后每次使用Spring Boot整合其他技术时首先需要考虑导入启动器。

1
2
3
4
5
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>

1、创建Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bitzh.springboot.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @Auther: oyy0v0
* @Date: 2024/8/7 - 08 - 07 - 22:05
* @Description: com.bitzh.springboot.controller
* @version: 1.0
*/
@RestController
@RequestMapping("/hello")
public class HelloHandler {

@GetMapping("/index")
public String index(){
return "Hello Spring Boot";
}
}

2、创建启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.bitzh.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}

}

启动类必须覆盖所有与业务相关的类:启动类所在的包必须是业务类所在包的同包或者父包。如果没有覆盖,业务类就不会自动装配到IoC容器中。

Spring Boot配置文件

自定义banner

image-20240807223454100

Properties

1
2
3
4
5
6
7
8
9
10
11
12
spring.application.name=springboot
#端口
server.port=8181
#项目访问路径
server.servlet.context-path=/springboot
#cookie失效时间
server.servlet.session.cookie.max-age=100
#session失效时间
server.servlet.session.timeout=100
#编码格式
server.tomcat.uri-encoding=utf-8

YAML

YAML是不同于Properties的另外一种文件格式,同样可以用来写配置文件,SpringBoot默认支持Yaml格式。

YAML格式的优点在于编写简单,结构清晰,利用缩进的形式来表示层级关系。

相比于Properties,YAML可以进一步简化配置文件的编写,更加方便。

1
2
3
4
5
6
7
8
9
10
server:
port: 8181
servlet:
context-path: /springboot
session:
cookie:
max-age: 100
timeout: 100
tomcat:
uri-encoding: utf-8

需要注意的是YAML格式书写非常严格,属性名和属性值之间必须至少有一个空格

如果Properties和YAML同时存在,Properties优先级更高

配置文件除了可以放在resources路径下之外,还有四个地方可以放置

image-20240808103936412

这四个地方的优先级是

1、根路径下config里面的yaml

2、根路径下的yaml

3、resources路径下的config中的yaml

4、resources路径下的yaml

可以直接在Handler中读取yaml文件中的数据,比如我们在业务方法中向客户端返回当前服务端的端口信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.bitzh.springboot.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @Auther: oyy0v0
* @Date: 2024/8/7 - 08 - 07 - 22:05
* @Description: com.bitzh.springboot.controller
* @version: 1.0
*/
@RestController
@RequestMapping("/hello")
public class HelloHandler {

/**
* SpEL Spring Expression Language
*/
@Value("${server.port}")
private String port;

@GetMapping("/index")
public String index(){
return "当前输入的端口"+this.port;
}
}

@Value注解同样适用于properties文件

SpringBoot项目结构

image-20240809161809150

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
-- 项目名

--src

--main

--java

java代码

--resources

--public 公共资源。所有共享的内容。对外公开的内容。(访问的时候不需要写public这个路径,直接写里面的文件名+后缀就可以访问到了)

--static静态资源。图片、js、css。不会被服务器解析。(访问的时候不需要写static这个路径,直接写里面的文件名+后缀就可以访问到了)

--js

-- jquery.js 访问:http://ip:port/js/jquery.js

注意:该目录是SpringBoot可以直接识别的目录,会将其中的

静态资源编译到web项目中,并放到tomcat中使用。静态资源的

访问路径中无需声明static 例如:localhost:8080/a.png

--templates

FreeMarker thymeleaf 页面所在目录。

--webapp 只有当页面使用jsp时才有。(已经不推荐用jsp了,所以默认没有)

--WEB-INF

Springboot_整合MyBatis

1、导入依赖pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--mybatis启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>

<!--mysql启动器-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>

<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

2、编写mybatis的配置文件

appliction.yml中添加如下配置

1
2
3
mybatis:
mapper-locations: classpath:com.bitzh.mapper/*.xml
type-aliases-package: com.bitzh.pojo
1
mapper-locations: classpath:com.bitzh.mapper/*.xml mapper映射文件包扫描
1
type-aliases-package 实体类别名包扫描

3、编写Controlelr层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.bitzh.controller;

import com.bitzh.pojo.User;
import com.bitzh.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/9 16:51
*/
@Controller
public class UserController {

@Autowired
private UserService userService;


@RequestMapping("/findAll")
@ResponseBody
public List<User> findAll(){


return userService.findAll();
}
}

4、UserService.java

1
2
3
4
public interface UserService {
public List<User> findAll();

}

5、UserServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.bitzh.service.impl;

import com.bitzh.mapper.UserMapper;
import com.bitzh.pojo.User;
import com.bitzh.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/9 16:53
*/
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserMapper userMapper;

@Override
public List<User> findAll() {
return userMapper.selectAll();
}
}

6、UserMapper.xml

1
2
3
4
5
6
7
8
9
10
<?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.bitzh.mapper.UserMapper">

<select id="selectAll" resultType="user">
select * from t_user
</select>

</mapper>

7、启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.bitzh;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;


// Generated by https://start.springboot.io
// 优质的 spring/boot/data/security/cloud 框架中文文档尽在 => https://springdoc.cn
@SpringBootApplication
@ComponentScan("com.bitzh.mapper")
public class Springboot02Application {

public static void main(String[] args) {
SpringApplication.run(Springboot02Application.class, args);
}

}

注意@ComponentScan(“com.bitzh.mapper”)这里要写扫描的mapper路径,或者在mapper的接口里面添加@Mapper注解否则扫描不到,但是有时候@Mapper注解也是不起作用,看情况使用(两个不能一起用)

其次在springboot中,mapper的映射文件的名字可以不一致,实际上是根据xml中的namespace来进行映射的,所以文件名不一致也没事。

image-20240809230849671

Springboot_整合logback

Springboot默认使用logback组件作为日志管理,logback是由log4j创世人设计的一个开源日志组件,不需要导入依赖,默认已经在starter里面了

使用步骤

1、在resources里面新建logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="${catalina.base}/logs/" />
<!-- 控制台输出 -->
<appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志输出格式 -->
<layout class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</layout>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/server.%d{yyyy-MM-dd}.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</layout>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="info">
<appender-ref ref="Stdout" />
<appender-ref ref="RollingFile" />
</root>
<logger name="com.msb.mapper" level="DEBUG"></logger>
<!--日志异步到数据库 -->
<!--<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
日志异步到数据库
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
连接池
<dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
<driverClass>com.mysql.jdbc.Driver</driverClass>
<url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
<user>root</user>
<password>root</password>
</dataSource>
</connectionSource>
</appender> -->
</configuration>

Springboot_整合PageHelper

PageHelper插件
我们在正常的查询业务之中,只需要加上一行代码就可以实现分页的数据的封装处理

1、先完成基础的查询所有员工的业务(这里就不演示了太简单了)

2、添加pageHelper的启动器依赖

1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>

3、controller层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.bitzh.controller;

import com.bitzh.pojo.Emp;
import com.bitzh.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/10 2:27
*/
@Controller
@RequestMapping("/emp")
public class EmpController {

@Autowired
private EmpService empService;


@ResponseBody
@RequestMapping("/findAll")
public List<Emp> findAll(){
return empService.findAll();
}

@ResponseBody
@RequestMapping("/findByPage/{pageNum}/{pageSize}")
public List<Emp> findByPage(@PathVariable("pageNum") Integer pageNum,@PathVariable("pageSize") Integer PageSize){
return empService.findByPage(pageNum,PageSize);
}


}

4、Service层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.bitzh.service.impl;

import com.bitzh.mapper.EmpMapper;
import com.bitzh.pojo.Emp;
import com.bitzh.service.EmpService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/10 2:28
*/
@Service
public class EmpServiceImpl implements EmpService {

@Autowired
private EmpMapper empMapper;

@Override
public List<Emp> findAll() {
return empMapper.findAll();
}

@Override
public List<Emp> findByPage(Integer pageNum, Integer pageSize) {
Page<Object> page = PageHelper.startPage(pageNum, pageSize);

List<Emp> list = empMapper.findAll();
// 获取 页码 页大小 当前页数据 总页数 总记录数 方式1 这一次请求,会被清掉的,只是临时的所以不推荐
System.out.println("当前页:"+page.getPageNum());
System.out.println("总页数"+page.getPages());
System.out.println("页大小:"+page.getPageSize());
System.out.println("总记录数:"+page.getTotal());
//这里的getResult和list的结果是一样的
System.out.println("当前页数据"+page.getResult());

//pageInfo 》》》 pageBean 方式2(推荐)
PageInfo<Emp> pi = new PageInfo<>(list);
System.out.println("当前页"+pi.getPageNum());
System.out.println("总页数"+pi.getPages());
System.out.println("页大小"+pi.getSize());
System.out.println("总记录数"+pi.getTotal());
System.out.println("当前页数据"+pi.getList());



return list;
}
}

PageInfo对象和Page对象的区别

1
https://blog.csdn.net/weixin_43760541/article/details/107155386 

Springboot_整合Druid

Druid是由阿里巴巴推出的数据库连接池。它结合了C3P0、DBCP、PROXOOL等数据库连接池的优点。之所以从众多数据库连接池中脱颖而出,还有一个重要的原因就是它包含控制台,很方便的帮助我们实现对于sql执行的监控。

1、添加依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>

2、修改配置文件application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
spring:
datasource:
# 使用阿里的Druid连接池
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 填写你数据库的url、登录名、密码和数据库名
url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: root
druid:
# 连接池的配置信息
# 初始化大小,最小,最大
initial-size: 5
min-idle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
# 配置DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
# 配置DruidStatViewServlet
stat-view-servlet:
url-pattern: "/druid/*"
# IP白名单(没有配置或者为空,则允许所有访问)
allow: 127.0.0.1,192.168.8.109
# IP黑名单 (存在共同时,deny优先于allow)
deny: 192.168.1.188
# 禁用HTML页面上的“Reset All”功能
reset-enable: false
# 登录名
login-username: admin
# 登录密码
login-password: 123456

Springboot整合JSP(了解一下就好)

因为JSP技术基本上已经淘汰了

1、添加依赖

1
2
3
4
5
6
<!--JSP依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>

2、添加webapps目录并设置目录

image-20240810105845969

3、设置工作目录,如果在IDEA中项目结构为聚合工程。那么在运行jsp是需要指定路径。如果项目结构为独立项目则不需要。

image-20240810105952730

4、在 yml配置文件中配置视图解析器参数

image-20240810110016720

后面跟springmvc类似

springboot整合FreeMarker(jsp的替代技术)

FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

常用的java模板引擎还有哪些?
Jsp、Freemarker、Thymeleaf 、Velocity 等。

模板+数据模型=输出
freemarker并不关心数据的来源,只是根据模板的内容,将数据模型在模板中显示并输出文件(通常为html,也可以生成其它格式的文本文件)
freemarker作为springmvc一种视图格式,默认情况下SpringMVC支持freemarker视图格式。 需要创建Spring Boot+Freemarker工程用于测试模板。

image-20240810111000993

使用步骤

1、配置依赖pom.xml

1
2
3
4
5
<!--freeMaker依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

阅读官方文档知道

image-20240810112018683

通过查阅配置信息发现,默认前缀为 ‘’ ,后缀为.ftlh,默认路径为templates

2、在show.ftlh中测试一下

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is ${msg}Page

</body>
</html>

3、controller层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.bitzh.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/10 13:17
*/
@Controller
public class FreemarkerController {


@ResponseBody
@RequestMapping("/show")
public ModelAndView freemarker(ModelAndView modelAndView) {
//绑定视图信息
modelAndView.setViewName("show");
//填充业务数据,也就是在show.ftlh中填充一个叫msg的里面实际值为freemarker的数据
modelAndView.addObject("msg", "freemarker");

return modelAndView;
}

}

测试成功

FreeMarker 常用指令

遍历List集合

1、创建controller利用之前的userService方法

1
2
3
4
5
6
7
8
9
10
11
12
@Autowired
private EmpService empService;

@RequestMapping("/showEmp")
//查询全部员工信息,展示
public ModelAndView testList(){
ModelAndView modelAndView = new ModelAndView();
List<Emp> empList = empService.findAll();
modelAndView.addObject("empList", empList);
modelAndView.setViewName("showEmp");
return modelAndView;
}

2、创建showEmp.flth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
#empTable{
width: 80%;
border: 1px solid blue;
margin: 0px auto;
}
#empTable th,td{
border: 1px solid green;
text-align: center;
}
</style>
</head>
<body>
<table id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>工号</th>
<th>姓名</th>
<th>岗位</th>
<th>薪资</th>
<th>部门号</th>
</tr>

</table>
</body>
</html>

3、在showEmp.flth中加入数据

1、注释,即

1
<#‐‐和‐‐>

,介于其之间的内容会被freemarker忽略
2、插值(Interpolation):即

1
${..}部分,freemarker会用真实的值代替${..}

3、FTL指令:和HTML标记类似,名字前加#予以区分,Freemarker会解析标签中的表达式或逻辑。
4、文本,仅文本信息,这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析,直接输出内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
#empTable{
width: 80%;
border: 1px solid blue;
margin: 0px auto;
}
#empTable th,td{
border: 1px solid green;
text-align: center;
}
</style>
</head>
<body>
<table id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>工号</th>
<th>姓名</th>
<th>岗位</th>
<th>薪资</th>
<th>部门号</th>
</tr>
<#--这里就类似于for(Emp emp : empList)-->
<#list empList as emp>
<tr>
<td>${emp_index}</td>
<td>${emp.empno}</td>
<td>${emp.ename}</td>
<td>${emp.job}</td>
<td>${emp.sal}</td>
<td>${emp.deptno}</td>
</tr>
</#list>


</table>
</body>
</html>

说明:_ index:得到循环的下标,使用方法是在stu后边加”_index”,它的值是从0开始

遍历Map集合

1、创建Controlelr层

FreeMarker在遍历map集合是,key必须是String

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Autowired
private EmpService empService;

@RequestMapping("/showEmpMap")
//查询全部员工信息,展示
public ModelAndView testMap(){
ModelAndView modelAndView = new ModelAndView();
List<Emp> empList = empService.findAll();

Map<String,Emp> empMap = new HashMap<>();
for (Emp emp : empList) {
empMap.put(emp.getEmpno().toString(), emp);
}


modelAndView.addObject("empMap", empMap);
modelAndView.setViewName("showEmpMap");
return modelAndView;
}

2、创建showEmpMap.ftlh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
#empTable{
width: 80%;
border: 1px solid blue;
margin: 0px auto;
}
#empTable th,td{
border: 1px solid green;
text-align: center;
}
</style>
</head>
<body>
<#--查看Map集合中的一个元素-->
查看7521员工信息
工号:${empMap['7521'].empno}<br/>
姓名:${empMap['7521'].ename}<br/>
岗位:${empMap['7521'].job}<br/>
薪资:${empMap['7521'].sal}<br/>
部门号:${empMap['7521'].deptno}<br/>

<hr/>
<table id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>工号</th>
<th>姓名</th>
<th>岗位</th>
<th>薪资</th>
<th>部门号</th>
</tr>
<#list empMap?keys as k>
<tr>
<#--这个keys也是个集合-->
<td>${k_index}</td>
<td>${k}</td>
<td>${empMap[k].ename}</td>
<td>${empMap[k].job}</td>
<td>${empMap[k].sal}</td>
<td>${empMap[k].deptno}</td>
</tr>
</#list>



</table>
</body>
</html>

if指令

如果在设置ModelAndView的时候,list集合为空的话会报错,这时候特别不友好,不展示就好了,所以就需要判断空值

if 指令即判断指令,是常用的FTL指令,freemarker在解析时遇到if会进行判断,条件为真则输出if中间的内容,否 则跳过内容不再输出。

如何判断空值
1、判断某变量是否存在使用 “??” 用法为:variable??,如果该变量存在,返回true,否则返回false 例:为防止stus为空报错可以加上判断如下
2、缺失变量默认值使用 “!” 使用!要以指定一个默认值,当变量为空时显示默认值。例: ${name!’’}表示如果name为空显示空字符串。如果是嵌套对象则建议使用()括起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
#empTable{
width: 80%;
border: 1px solid blue;
margin: 0px auto;
}
#empTable th,td{
border: 1px solid green;
text-align: center;
}
</style>
</head>
<body>
<table id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>工号</th>
<th>姓名</th>
<th>岗位</th>
<th>薪资</th>
<th>上级</th>
<th>部门号</th>
</tr>
<#if empList??>

<#--这里就类似于for(Emp emp : empList)-->
<#list empList as emp>
<tr>
<td>${emp_index}</td>
<td>${emp.empno}</td>
<td>${emp.ename}</td>
<td>${emp.job}</td>
<td>${emp.sal}</td>
<td>${emp.mgr!'没有'}</td>
<td>${emp.deptno}</td>
</tr>
</#list>
</#if>


</table>
</body>
</html>

if中支持的运算符
a算数运算符 FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:+, - , * , / , %
b逻辑运算符有如下几个: 逻辑与:&& 逻辑或:|| 逻辑非:! 逻辑运算符只能作用于布尔值,否则将产生错误
c比较运算符有如下几个:
① =或者==:判断两个值是否相等.
② !=:判断两个值是否不等.
③ > 或者gt:判断左边值是否大于右边值
④ >=或者gte:判断左边值是否大于等于右边值
⑤ <或者lt:判断左边值是否小于右边值
⑥ <=或者lte:判断左边值是否小于等于右边值
注意: =和!=可以用于字符串,数值和日期来比较是否相等,但=和!=两边必须是相同类型的值,否则会产生错误,而且FreeMarker是精确比较,”x”,”x “,”X”是不等的.其它的运行符可以作用于数字和日期,但不能作用于字符串,大部分的时候,使用gt等字母运算符代替>会有更好的效果,因为 FreeMarker会把>解释成FTL标签的结束字符,当然,也可以使用括号来避免这种情况,如:<#if (x>y)>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<#if empList??>
<#list empList as emp>
<tr <#if emp_index%2 ==0 > style="background-color: gray" </#if>>
<td>${emp_index}</td>
<td>${emp.empno}</td>
<td <#if emp.ename == 'KING'> style="color: aqua" </#if>>${emp.ename}</td>
<td>${emp.job}</td>
<td>${emp.mgr!'无'}</td>
<td <#if emp.sal gte 2000.0> style="color: red" </#if>>${emp.sal}</td>
<td>${emp.comm!'0'}</td>
<td>${emp.deptno}</td>
</tr>
</#list>
</#if>

内置函数

内建函数语法格式: 变量+?+函数名称

1、内建函数获取某个集合的大小
${集合名?size}

2、内建函数日期格式化
显示年月日:

1
${today?date}

显示时分秒:

1
${today?time}

显示日期+时间:

1
${today?datetime}

自定义格式化:

1
${today?string("yyyy年MM月")}

3、内建函数将json字符串转成对象

1
2
3
<#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
<#assign data=text?eval />
开户行:${data.bank} 账号:${data.account}

其中用到了 assign标签,assign的作用是定义一个变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
员工人数:${empList?size}
<table id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>工号</th>
<th>姓名</th>
<th>岗位</th>
<th>上级</th>
<th>入职日期a</th>
<th>入职日期b</th>
<th>入职日期c</th>
<th>入职日期d</th>
<th>薪资</th>
<th>补助</th>
<th>部门号</th>
</tr>
<#if empList??>
<#list empList as emp>
<tr <#if emp_index%2 ==0 > style="background-color: gray" </#if>>
<td>${emp_index}</td>
<td>${emp.empno}</td>
<td <#if emp.ename == 'KING'> style="color: aqua" </#if>>${emp.ename}</td>
<td>${emp.job}</td>
<td>${emp.mgr!'无'}</td>
<td>${emp.hiredate?date}</td>
<td>${emp.hiredate?time}</td>
<td>${emp.hiredate?datetime}</td>
<td>${emp.hiredate?string("yyyy年MM月dd日")}</td>
<td <#if emp.sal gte 2000.0> style="color: red" </#if>>${emp.sal}</td>
<td>${emp.comm!'0'}</td>
<td>${emp.deptno}</td>
</tr>
</#list>
</#if>
</table>

内置函数

1
https://blog.csdn.net/chami_/article/details/51992044 

Springboot 整合Thymeleaf

Thymeleaf的主要目标是将优雅的自然模板带到开发工作流程中,并将HTML在浏览器中正确显示,并且可以作为静态原型,让开发团队能更容易地协作。Thymeleaf能够处理HTML,XML,JavaScript,CSS甚至纯文本。
长期以来,jsp在视图领域有非常重要的地位,随着时间的变迁,出现了一位新的挑战者:Thymeleaf,Thymeleaf是原生的,不依赖于标签库.它能够在接受原始HTML的地方进行编辑和渲染.因为它没有与Servelet规范耦合,因此Thymeleaf模板能进入jsp所无法涉足的领域。
Thymeleaf在Spring Boot项目中放入到resources/templates中。这个文件夹中的内容是无法通过浏览器URL直接访问的(和WEB-INF效果一样),所有Thymeleaf页面必须先走控制器。

image-20240810151419898

关于Thymeleaf默认配置

image-20240810151435554

1、添加Thymeleaf依赖

1
2
3
4
5
6
<!--Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.4.5</version>
</dependency>

2、写个html和controller测试

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is index page
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.bitzh.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/10 15:44
*/
@Controller
public class ThymeleafController {


@RequestMapping("/index")
public String showIndex(){
return "index";
}
}

Thymeleaf基础语法

Thymeleaf标准变量表达式

Thymeleaf通过标准变量表达式完成数据的展示和处理

1 标准变量表达式必须依赖标签,不能独立使用
2 标准变量表达式一般在开始标签中,以 th开头
3 语法为:

1
<tag th:***="${key}"   ></tag>

4 表达式中可以通过

1
${}

取出域中的值并放入标签的指定位置
5 ${}在这里不能单独使用,必须在 th:后面的双引号里使用

为了有提示,修改html页面中

1
<html>

标签为

1
<html xmlns:th="http://www.thymeleaf.org" >

1、th:text属性 (向html双标签内部输出信息)

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is index page<br/>
<span th:text="pageMessage"></span><br/>
<span th:text="${msg}"></span>
</body>
</html>

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.bitzh.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/10 15:44
*/
@Controller
public class ThymeleafController {


@RequestMapping("/index")
public String showIndex(Map<String, Object> map) {
map.put("msg", "testMessage");
return "index";
}
}

2、th:value

表单元素,设置HTML标签中表单元素value属性时使用

1
2
<input th:value="pageMessage"/>
<input th:value="${msg}"/>

3、th:if

因为如果emp为空的话会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
展示单个员工信息<br/>
<span th:if="${emp}!=null">
工号:<span th:text="${emp.getEmpno()}"></span><br/>
姓名:<span th:text="${emp.getEname()}"></span><br/>
职务:<span th:text="${emp.getJob()}"></span><br/>
上级:<span th:text="${emp.getMgr()}"></span><br/>
入职日期:<span th:text="${emp.getHiredate()}"></span><br/>
工资:<span th:text="${emp.getSal()}"></span><br/>
补助:<span th:text="${emp.getComm()}"></span><br/>
部门号:<span th:text="${emp.getDeptno()}"></span><br/>
</span>
</body>
</html>

但是即使这样如果写成emp[0].getEmpno会出现越界报错,如果是这样该怎么解决呢?再嵌套一层th:if然后用emp.size() != 0来进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
<span  th:if="${empList}!=null">
<span th:if="${empList.size()} ne 0">
工号:<span th:text="${empList[0].empno}"></span><br/>
姓名:<span th:text="${empList[0].ename}"></span><br/>
职务:<span th:text="${empList[0].job}"></span><br/>
上级:<span th:text="${empList[0].mgr}"></span><br/>
入职日期:<span th:text="${empList[0].hiredate}"></span><br/>
工资:<span th:text="${empList[0].sal}"></span><br/>
补助:<span th:text="${empList[0].comm}"></span><br/>
部门号:<span th:text="${empList[0].deptno}"></span><br/>
</span>
</span>

4、th:each标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
#empTable{
width: 80%;
border: 1px solid blue;
margin: 0px auto;
}
#empTable th,td{
border: 1px solid green;
text-align: center;
}
</style>
</head>
<body>
展示单个员工信息:
<span th:if="${emp}!=null">
工号:<span th:text="${emp.empno}"></span><br/>
姓名:<span th:text="${emp.ename}"></span><br/>
职务:<span th:text="${emp.job}"></span><br/>
上级:<span th:text="${emp.mgr}"></span><br/>
入职日期:<span th:text="${emp.hiredate}"></span><br/>
工资:<span th:text="${emp.sal}"></span><br/>
补助:<span th:text="${emp.comm}"></span><br/>
部门号:<span th:text="${emp.deptno}"></span><br/>
</span>
<hr/>
<span th:if="${empList}!=null">
<span th:if="${empList.size()} ne 0">
工号:<span th:text="${empList[0].empno}"></span><br/>
姓名:<span th:text="${empList[0].ename}"></span><br/>
职务:<span th:text="${empList[0].job}"></span><br/>
上级:<span th:text="${empList[0].mgr}"></span><br/>
入职日期:<span th:text="${empList[0].hiredate}"></span><br/>
工资:<span th:text="${empList[0].sal}"></span><br/>
补助:<span th:text="${empList[0].comm}"></span><br/>
部门号:<span th:text="${empList[0].deptno}"></span><br/>
</span>
</span>
<table id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>序号</th>
<th>总人数</th>
<th>偶数索引?</th>
<th>奇数索引?</th>
<th>第一?</th>
<th>最后?</th>
<th>工号</th>
<th>姓名</th>
<th>职务</th>
<th>上级</th>
<th>入职日期</th>
<th>工资</th>
<th>补助</th>
<th>部门号</th>
</tr>
<tr th:each="emp,i:${empList}">
<td th:text="${i.index}"></td>
<td th:text="${i.count}"></td>
<td th:text="${i.size}"></td>
<td th:text="${i.odd}"></td>
<td th:text="${i.even}"></td>
<td th:text="${i.first}"></td>
<td th:text="${i.last}"></td>
<td th:text="${emp.empno}"></td>
<td th:text="${emp.ename}"></td>
<td th:text="${emp.job}"></td>
<td th:text="${emp.mgr}"></td>
<td th:text="${emp.hiredate}"></td>
<td th:text="${emp.sal}"></td>
<td th:text="${emp.comm}"></td>
<td th:text="${emp.deptno}"></td>
</tr>

</table>
</body>
</html>

5、标准变量表达式运算符支持

标准变量表达式支持的运算符

算数运算符

1
算术运算:+ , - , * , / , %
1
2
3
4
<span th:text="1+1"></span>
<span th:text="'1'+1"></span>
<span th:text="${emp.empno}+1"></span>
<span th:text="${emp.empno+1}"></span>

关系运算符

1
2
3
4
5
6
1 gt:     great than(大于)>
2 ge: great equal(大于等于)>=
3 eq: equal(等于)==
4 lt: less than(小于)<
5 le: less equal(小于等于)<=
6 ne: not equal(不等于)!=

逻辑运算符

&& 或 and: 表示并且

|| 或 or : 表示或者

1
2
3
4
5
6
7
8
9
10
<div th:text="1>0 and 2<3"></div>
<div th:text="1>0 and 2>3"></div>
<div th:text="1>0 or 2<3"></div>
<div th:text="1>0 or 2>3"></div>
<hr/>
<div th:text="${emp.sal ge 800}"></div>
<div th:text="${emp.sal } ge 800"></div>
<div th:text="${emp.sal ge 800} and ${emp.deptno eq 20}"></div>
<div th:text="(${emp.sal }ge 800) or (${emp.deptno } ne 20)"></div>
<div th:text="${emp.sal ge 800 or emp.deptno ne 20 }"></div>

在早期的thymeleaf模板引擎框架中 逻辑运算符要写在${}的外边,目前我们2.4.5版本中,可以写在${}里面

三目运算符

1
<tr th:each="emp,i:${empList}" th:class="${i.odd}?a:b">

如果是偶数列就用a样式否则用b样式

对空值作出处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<tr th:each="emp,i:${empList}" th:class="${i.odd}?a:b">
<td th:text="${i.index}"></td>
<td th:text="${i.count}"></td>
<td th:text="${i.size}"></td>
<td th:text="${i.odd}"></td>
<td th:text="${i.even}"></td>
<td th:text="${i.first}"></td>
<td th:text="${i.last}"></td>
<td th:text="${emp.empno}"></td>
<td th:text="${emp.ename}"></td>
<td th:text="${emp.job}"></td>
<td th:text="${emp.mgr} eq null ?老板:${emp.mgr}"></td>
<td th:text="${emp.hiredate}"></td>
<td th:text="${emp.sal}"></td>
<td th:text="${emp.comm} eq null ?0:${emp.comm}"></td>
<td th:text="${emp.deptno}"></td>
</tr>

6、th:href

设置href属性的。取值使用@{}取跳转路径值,小括号里面写的是获取参数

1
2
3
4
5
6
<td>
<a th:href="@{/removeEmp(empno=7369,ename='SMITH')}">删除</a>
</td>
<td>
<a th:href="@{/removeEmp(empno=${emp.empno},ename=emp.ename)}">删除</a>
</td>

controller层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping("/showEmp")
public String showEmp(Map<String, Object> map) {
List<Emp> empList = empService.findAll();
map.put("empList", empList);
map.put("emp", empList.get(0));

return "showEmp";
}


@RequestMapping("/removeEmp")
public String removeEmp(Integer empno,String ename) {
System.out.println("remove");
return "redirect:showEmp";
}

7、th:onclick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<table  id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>序号</th>
<th>总人数</th>
<th>偶数索引?</th>
<th>奇数索引?</th>
<th>第一?</th>
<th>最后?</th>
<th>工号</th>
<th>姓名</th>
<th>职务</th>
<th>上级</th>
<th>入职日期</th>
<th>工资</th>
<th>补助</th>
<th>部门号</th>
<th>操作</th>
</tr>
<tr th:each="emp,i:${empList}" th:class="${i.odd}?a:b">
<td th:text="${i.index}"></td>
<td th:text="${i.count}"></td>
<td th:text="${i.size}"></td>
<td th:text="${i.odd}"></td>
<td th:text="${i.even}"></td>
<td th:text="${i.first}"></td>
<td th:text="${i.last}"></td>
<td th:text="${emp.empno}"></td>
<td th:text="${emp.ename}"></td>
<td th:text="${emp.job}"></td>
<td th:text="${emp.mgr} eq null ?老板:${emp.mgr}"></td>
<td th:text="${emp.hiredate}"></td>
<td th:text="${emp.sal}"></td>
<td th:text="${emp.comm} eq null ?0:${emp.comm}"></td>
<td th:text="${emp.deptno}"></td>
<td>
<a href="javascript:void(0)" th:onclick="removeEmp([[${emp.empno}]],[[${emp.ename}]])">删除</a>
</td>
</tr>

</table>
<script>
function removeEmp(empno,ename){
var resulet =confirm("确定要删除编号为"+empno+"的"+ename);
if(resulet){
window.location.href="removeEmp?empno="+empno+"&ename="+ename;
}
}
</script>

在th:onclick中要传参的话,参数用两个[]扩住就好了

Thymeleaf内置对象

Thymeleaf提供了一些内置对象,内置对象可直接在模板中使用。这些对象是以#引用的。
使用内置对象的语法
引用内置对象需要使用#
大部分内置对象的名称都以s结尾。如:strings、numbers、dates
常见内置对象如下

arrays:数组操作的工具;

aggregates:操作数组或集合的工具;

bools:判断boolean类型的工具;

calendars:类似于#dates,但是是java.util.Calendar类的方法;

ctx:上下文对象,可以从中获取所有的thymeleaf内置对象;

dates:日期格式化内置对象,具体方法可以参照java.util.Date;

numbers: 数字格式化;#strings:字符串格式化,具体方法可以参照String,如startsWith、contains等;

objects:参照java.lang.Object;

lists:列表操作的工具,参照java.util.List;

sets:Set操作工具,参照java.util.Set;#maps:Map操作工具,参照java.util.Map;

messages:操作消息的工具。

这里我们着重看 strings dates numbers和域对象

strings对象

image-20240811043705150

dates对象

image-20240811043732999

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#numbers

#numbers.formatDecimal(numbwe,整数位,整数位千分位标识符,小数位,小数位表示符)

${#numbers.formatDecimal(num,1,'COMMA',2,'POINT')}

显示:99,999,999.99

1:表示整数位至少一位,不足以0补齐,如:num = 0.00,

${#numbers.formatDecimal(num,0,'COMMA',2,'POINT')}则显示 .00

${#numbers.formatDecimal(num,1,'COMMA',2,'POINT')}则显示 0.00

COMMA:','

POINT:‘.’

域对象

image-20240811051038138

更多内置 对象:

1
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#number-literals

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("showIndex")
public String showIndex(Map<String,Object> map, HttpServletRequest req, HttpSession session){
// 向request域放数据
req.setAttribute("msg", "requestMessage");
// 向session域放数据
session.setAttribute("msg", "sessionMessage");
// 向application域放数据
req.getServletContext().setAttribute("msg", "applicationMessage");
// 对象List集合数据
List<Emp> empList = empService.findAll();
map.put("empList", empList);
return "index";
}

页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
#empTable{
width: 80%;
border: 1px solid blue;
margin: 0px auto;
}
#empTable th,td{
border: 1px solid green;
text-align: center;
}
.a{
background-color: antiquewhite;
}
.b{
background-color: gray;
}
</style>
</head>
<body>
<table id="empTable" cellpadding="0px" cellspacing="0px">
<tr>
<th>索引</th>
<th>序号</th>
<th>总人数</th>
<th>偶数索引?</th>
<th>奇数索引?</th>
<th>第一?</th>
<th>最后?</th>
<th>工号</th>
<th>姓名</th>
<th>职务</th>
<th>上级</th>
<th>入职日期</th>
<th>入职年</th>
<th>入职月</th>
<th>入职日</th>
<th>工资</th>
<th>补助</th>
<th>部门号</th>
<th>操作</th>
</tr>
<tr th:each="emp,i:${empList}" th:class="${i.odd}?a:b">
<td th:text="${i.index}"></td>
<td th:text="${i.count}"></td>
<td th:text="${i.size}"></td>
<td th:text="${i.odd}"></td>
<td th:text="${i.even}"></td>
<td th:text="${i.first}"></td>
<td th:text="${i.last}"></td>
<td th:text="${emp.empno}"></td>
<td th:text="${emp.ename}"></td>
<td th:text="${emp.job}"></td>
<td th:text="${#strings.isEmpty(emp.mgr)}?老板:${emp.mgr}"></td>
<td th:text="${#dates.format(emp.hiredate,'yyyy-MM-dd HH:mm:ss')}"></td>
<td th:text="${#dates.year(emp.hiredate)}"></td>
<td th:text="${#dates.month(emp.hiredate)}"></td>
<td th:text="${#dates.day(emp.hiredate)}"></td>
<td th:text="${#numbers.formatDecimal(emp.sal,7,'COMMA',2,'POINT')}"></td>
<td th:text="${#strings.isEmpty(emp.comm)}?0:${#numbers.formatDecimal(emp.sal,7,'COMMA',2,'POINT')}"></td>
<td th:text="${emp.deptno}"></td>
<td>
<a href="javascript:void(0)" th:onclick="removeEmp([[${emp.empno}]],[[${emp.ename}]])">删除</a>
</td>
</tr>

</table>
<script>
function removeEmp(empno,ename){
var resulet =confirm("确定要删除编号为"+empno+"的"+ename);
if(resulet){
window.location.href="removeEmp?empno="+empno+"&ename="+ename;
}
}
</script>
<hr/>
request:<br/>
<span th:text="${#httpServletRequest.getAttribute('msg')}"></span><br/>
<span th:text="${#request.getAttribute('msg')}"></span><br/>
<span th:text="${msg}"></span><br/>
session:<br/>
<span th:text="${#httpSession.getAttribute('msg')}"></span><br/>
<span th:text="${#session.getAttribute('msg')}"></span><br/>
<span th:text="${session.msg}"></span><br/>
application:<br/>
<span th:text="${#servletContext.getAttribute('msg')}"></span><br/>
<span th:text="${application.msg}"></span><br/>
</body>
</html>

模板引擎总结

jsp
优点:
1、功能强大,可以写java代码
2、支持jsp标签(jsp tag)
3、支持表达式语言(el)
4、官方标准,用户群广,丰富的第三方jsp标签库
缺点:
性能问题。不支持前后端分离

freemarker
FreeMarker是一个用Java语言编写的模板引擎,它基于模板来生成文本输出。FreeMarker与Web容器无关,即在Web运行时,它并不知道Servlet或HTTP。它不仅可以用作表现层的实现技术,而且还可以用于生成XML,JSP或Java 等。
目前企业中:主要用Freemarker做静态页面或是页面展示

优点:
1、不能编写java代码,可以实现严格的mvc分离
2、性能非常不错
3、对jsp标签支持良好
4、内置大量常用功能,使用非常方便
5、宏定义(类似jsp标签)非常方便
6、使用表达式语言
缺点:
1、不是官方标准
2、用户群体和第三方标签库没有jsp多

Thymeleaf
Thymeleaf是个XML/XHTML/HTML5模板引擎,可以用于Web与非Web应用。
Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。Thymeleaf的可扩展性也非常棒。你可以使用它定义自己的模板属性集合,这样就可以计算自定义表达式并使用自定义逻辑。这意味着Thymeleaf还可以作为模板引擎框架。
优点:静态html嵌入标签属性,浏览器可以直接打开模板文件,便于前后端联调。springboot官方推荐方案。
缺点:模板必须符合xml规范

VUE: 前后端分离,最多,未来趋势

springboot_开发者工具

使用开发者工具包不需要重启。监听内容改变。

使用步骤

1、导入依赖

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.4.5</version>
<optional>true</optional>
</dependency>

2、修改idea自动编译

image-20240811053432548

2、修改Reigstry

Ctrl+Shift+Alt+/ 点击弹出框中Registry…

image-20240811053447655

springboot_打包部署

SpringBoot项目可以是jar类型的maven项目,也可以是一个war类型的maven项目,取决于我们要不要整合jsp使用。但是不管是哪种项目类型,已经不是我们传统意义上的项目结构了
在本地使用SpringBoot的启动器即可访问我们开发的项目。如果我们将项目功能开发完成后,需要使用SpringBoot的打包功能来将项目进行打包。
SpringBoot项目打包在linux服务器中运行:
①jar类型项目会打成jar包:
jar类型项目使用SpringBoot打包插件打包时,会在打成的jar中内置一个tomcat的jar。所以我们可以使用jdk直接运行该jar项目可,jar项目中有一个功能,将功能代码放到其内置的tomcat中运行。我们直接使用浏览器访问即可。
②war类型项目会打成war包:
在打包时需要将内置的tomcat插件排除,配置servlet的依赖。将war正常的放到tomcat服务器中运行即可。

jar和war区别就是jar可以直接在浏览器运行,war需要再tomgcat中运行

使用步骤

1、导入springboot打包插件

1
2
3
4
5
6
7
8
9
10
11
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>

将项目导出成jar包并运行
项目类型为jar

image-20240811054409027

使用maven package指令打包即可

image-20240811054426485

打成包后,可以通过dos java -jar指令直接启动运行

image-20240811054443552

将项目导出war包并运行
项目打包成war之后,要放在一个Tomcat上运行

如果我们当前的maven项目本身就是war类型的项目,直接打包即可,但是如果我们当前的maven项目是jar类型的项目,我们需要将项目修改为war类型,修改项目的pom文件,使用packaging标签设置值为war.并且需要在项目中创建webapp文件夹,并设置为资源文件夹。

image-20240811054938652

排除项目中自带的所有的Tomcat插件和jsp servlet 依赖,因为这里要将项目放到一个Tomcat上运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--配置SpringBoot的web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除web启动中自动依赖的tomcat插件-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--
手动依赖tomcat插件,但是表明项目打包时该依赖不会被打进去,目的主要是保证开发阶段本地SpringBoot
项目可以正常运行
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<!--打包的时候可以不用包进去,别的设施会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。
相当于compile,但是打包阶段做了exclude操作-->
<scope>provided</scope>
</dependency>

SpringBoot的启动类继承SpringBootServletInitializer,并重写configure

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
//重写配置方法
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
//启动SpringBoot
SpringApplication.run(MyApplication.class,args);
}
}

使用install命令打包项目,并将war包放到tomcat下的webapps下,启动tomcat即可。

image-20240811055225335

Springboot异常处理

默认情况,Spring Boot项目错误页面如下。当项目实际上线,如果给用户显示这个页面就不是很友好。当系统出现异常时应该给用户显示更加友好的错误页面。

设置具体的状态码页面
在templates/下新建error文件夹,在error中新建:状态码.html的页面。例如当出现500时显示的页面为500.html

使用x进行模糊匹配
当出现5开头状态码的错误时,显示页面可以命名为5xx.html
当出现50开头状态码的错误时,显示页面可以命名为50x.html

统一错误显示页面
在templates下新建error.html。如果项目中不存在具体状态码的页面或没有使用x成功匹配的页面时,显示error.html作为错误显示页面。

优先级就是有精确的就精确,没有精确的就模糊再到统一的。

SpringMVC异常简介
系统中异常包括两类:预期异常(检查型异常)和运行时异常 RuntimeException,前者通过捕获异常从而获取异常信息, 后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由 springmvc 前端控制器交由异常处理器进行异常处理,如下图

image-20240811063108648

异常处理具体实现

1使用@ExceptionHandler注解处理异常

缺点:只能处理当前Controller中的异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Controller
public class ControllerDemo1 {
@RequestMapping("test1.action")
public String test1(){
int i = 1/0;
return "success";
}
@RequestMapping("test2.action")
public String test2(){
String s =null;
System.out.println(s.length());
return "success";
}
@ExceptionHandler(value ={ArithmeticException.class,NullPointerException.class} )
public ModelAndView handelException(){
ModelAndView mv =new ModelAndView();
mv.setViewName("error1");
return mv;
}
}

2使用:@ControllerAdvice+@ExceptionHandler

此处优先级低于局部异常处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.msb.exceptionhandler;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
/**
* @Author: Ma HaiYang
* @Description: MircoMessage:Mark_7001
*/
@ControllerAdvice
public class GloableExceptionHandler1 {
@ExceptionHandler(value ={ArithmeticException.class,NullPointerException.class} )
public ModelAndView handelException(){
ModelAndView mv =new ModelAndView();
mv.setViewName("error1");
return mv;
}
}

3使用:SimpleMappingExceptionResolver

xml配置
配置类配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 全局异常
*/
@Configuration
public class GloableException2 {
@Bean
public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.put("java.lang.NullPointerException","error1");
prop.put("java.lang.ArithmeticException","error2");
resolver.setExceptionMappings(prop);
return resolver;
}
}

4自定义的HandlerExceptionResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 全局异常
* HandlerExceptionResolve
*/
@Configuration
public class GloableException3 implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView mv = new ModelAndView();
if(e instanceof NullPointerException){
mv.setViewName("error1");
}
if(e instanceof ArithmeticException){
mv.setViewName("error2");
}
mv.addObject("msg",e);
return mv;
}}

Spring Boot中Bean管理

Spring Boot 由于没有XML文件,所以所有的Bean管理都放入在一个配置类中实现。
配置类就是类上具有@Configuration的类。这个类就相当于之前的applicationContext.xml

1 新建配置类
com.bitzh.config.MyConfig , 规范都是放入到config文件夹中。
注意:配置类要有@Configuration,方法要有@Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class MyConfig {
//访问权限修饰符没有强制要求,一般是protected
//返回值就是注入到Spring容器中实例类型。
// 方法名没有强制要求,相当于<bean >中id属性。
@Bean
protected User getUser(){
User user = new User();
user.setId(1L);
user.setName("张三");
return user;
}
//自定义bean名称
@Bean("user2")
protected User getUser2(){
User user = new User();
user.setId(2L);
user.setName("李四");
return user;
}
}

如果Spring容器中存在同类型的Bean通过Bean的名称获取到Bean对象。或结合@Qualifier使用

1
2
3
4
5
6
7
8
9
10
11
@SpringBootTest
public class TestGetBean {
@Autowired
@Qualifier("user2")
private User user;
@Test
public void testGetUser(){
System.out.println(user);
}
}

在配置类的方法中通过方法参数让Spring容器把对象注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//自定义bean名称
@Bean("user1")
public User getUser(){
User user = new User();
user.setId(2L);
user.setName("李四");
return user;
}
@Bean
//可以直接从方法参数中取到。
public People getPeople(User user1){
People p = new People();
p.setUser(user1);
return p;
}

springboot_单元测试

在src/main/test里面新建com.bitzh.项目上下文

注意:

  1. 测试类不能叫做Test,会和注解同名
  2. 测试方法必须是public
  3. 测试方法返回值必须是void
  4. 测试方法必须没有参数
1
2
3
4
5
6
7
8
9
10
@SpringBootTest(classes = Springboot03Application.class)
class Springboot03AppliactionTests {
@Autowired
private EmpService empService;
@Test
public void testFindAll() {
List<Emp> list = empService.findAll();
list.forEach(System.out::println);
}
}

Springboot_拦截器

新建拦截器类。
注意:不要忘记类上注解@Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bitzh.intercepter;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/11 6:57
*/
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("执行拦截器");
return true;
}
}

配置拦截器
注意:类上有注解@Configuration。此类相当于SpringMVC配置文件。
addPathPattern(): 拦截哪些URL。 /** 拦截全部
excludePathPatterns(): 不拦截哪些URL。当和addPathPattern()冲突时,excludePathPatterns()生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.bitzh.intercepter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/11 6:58
*/
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor;
//配置拦截器的映射
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}
}

springboot_注解

springboot默认已经帮助我们整合好了SpringMVC,同时也给我们默认配置了DispathcerServlet 和编码过滤器,同时也给我们配置好了WEB项目开发的常见组件
查看容器中的所有组件

@SpringBootApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* 默认扫描启动类所在包下的所有层级的子包
* 可以通过scanBasePackages属性指定扫描路径
* SpringBootApplication是一个合成注解,可以拆分为以下三个注解
* @SpringBootConfiguration
* @EnableAutoConfiguration
* @ComponentScan(basePackages = "com.bitzh")
*
*
* */
@SpringBootApplication
public class Springboot04Application {
public static void main(String[] args) {
//返回一个spring容器
ConfigurableApplicationContext context = SpringApplication.run(Springboot04Application.class, args);
// 查看所有组件的名
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}

@Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

/**
* MyConfig配置类本身也是一个spring容器中的bean
* proxyBeanMethods=true 属性,给MyConfig对象产生一个代理对象
* 通过代理对象控制反复调用MyConfig里面的方法返回的是容器中的一个单实例
* 如果proxyBeanMethods=false 那么我们拿到的MyConfig对象就不是一个代理对象
* 那么这个时候反复调用MyConfig中的方法返回的就是多实例
*
* proxyBeanMethods=false 称之为Lite模式 特点启动快
* proxyBeanMethods=true 称之为Full模式 特点依赖spring容器控制bean单例
*
*/
@Configuration(proxyBeanMethods = true)
public class MyConfig {
/*<bean id = "user1" class ="com.msb.pojo.User">... ...</bean>*/
@Bean // 向容器中添加一个Bean,以方法名作为Bean的id,返回值类型作为组件的类型
public User user1(){
return new User("zhangsan", 10);
}
/*<bean id = "user2" class ="com.msb.pojo.User">... ...</bean>*/
@Bean("user2") // 向容器中添加一个Bean,手动指定Bean的name属性,返回值类型作为组件的类型
public User getUser(){
return new User("lisi", 11);
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication(scanBasePackages = "com.bitzh")
public class Springboot04Application {
public static void main(String[] args) {
//返回一个spring容器
ConfigurableApplicationContext context = SpringApplication.run(Springboot04Application.class, args);

System.out.println(context.getBean("user1"));
System.out.println(context.getBean("user2"));
User usera = context.getBean(MyConfig.class).getUser();
User userb = context.getBean(MyConfig.class).getUser();
System.out.println(usera==userb);//true
}
}

image-20240811072049058

@Import

1
2
3
4
5
6
7
8
9
10
/*
* @Import({User.class}) 在容器中自动创建Bean的注解
* 通过传入字节码,默认调用bean的无参构造器,向容器中存放一个Bean
* 默认组件的名字就是类的全路径名
* @Import只要放到可以被扫描到的类之上就可以,不必非得是配置类或者Controller
* */
@Import({User.class})
@Configuration(proxyBeanMethods = true)
public class MyConfig {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootApplication(scanBasePackages = "com.msb")
public class Springboot04Application {
public static void main(String[] args) {
//启动SpringBoot, 返回一个spring容器
ConfigurableApplicationContext context = SpringApplication.run(Springboot04Application.class, args);
// 根据类型获取Bean
User bean = context.getBean(User.class);
System.out.println(bean);
// 获取属性User类的所有bean的name
String[] beanNamesForType = context.getBeanNamesForType(User.class);
for (String s : beanNamesForType) {
System.out.println(s);
}
}
}

@Conditional 条件装配

满足Conditional指定的条件,则进行组件注入
@Conditional下还有很多子注解

image-20240811073351887

image-20240811073401448

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
/*
* 当配置中存在一个aaa.b的配置并且值为x的时候,当前配置中所有的配置才会生效
* */
//@ConditionalOnProperty( name = "aaa.b",havingValue = "x")
public class MyConfig {
/*
* 当配置中存在一个aaa.b的配置并且值为x的时候,容器才会初始化user1
* */
@ConditionalOnProperty( name = "aaa.b",havingValue = "y")
@Bean
public User user1(){
return new User("zhangsan", 10);
}
}

image-20240811073424601

这样User1就注入成功了

@ImportResource

原生配置文件引入,允许我们自己定义xml配置文件,在文件中配置bean
resources目录下准备一个xml配置文件

image-20240811074058129

配置类中加载该配置文件

1
2
3
4
5
import org.springframework.context.annotation.*;
@Configuration
@ImportResource("classpath:beans.xml")
public class MyConfig {
}

从容器中获取beans.xml中配置的bean

1
2
3
4
5
6
7
8
@SpringBootApplication
public class Springboot04Application {
public static void main(String[] args) {
//启动SpringBoot, 返回一个spring容器
ConfigurableApplicationContext context = SpringApplication.run(Springboot04Application.class, args);
System.out.println(context.getBean("userx"));
}
}

@ConfigurationProperties

读取application.properties配置文件中的内容,读取进入bean

image-20240811074156646

1
2
3
4
5
6
7
8
/*prefix前缀,为配置文件中对应的前缀
* 通过前缀找到对应的配置信息后,在根据属性名去注入匹配的数据*/
@ConfigurationProperties( prefix = "user")
@Component
public class User {
private String uname;
private int age;
}

配置类代码

1
2
3
4
5
6
7
8
@Configuration
/*开启了User的属性自动配置功能并把User自动注册到容器中
* 这个时候,我们的User上就不用加@Component注解了
* 适用于Bean来自于第三方JAR场景
* */
@EnableConfigurationProperties(User.class)
public class MyConfig {
}

测试代码

1
2
3
4
5
6
7
8
@SpringBootApplication
public class Springboot04Application {
public static void main(String[] args) {
//启动SpringBoot, 返回一个spring容器
ConfigurableApplicationContext context = SpringApplication.run(Springboot04Application.class, args);
System.out.println(context.getBean(User.class));
}
}

springboot_静态资源

静态资源默认存放位置

resources 目录下,static/public目录是我们的静态资源目录,直接访问该目录下的资源的映射路径不需要携带/public或者/static,直接访问即可

image-20240811094540231

请求进来,先去看Controller中有没有对应的资源,如果有则,执行controller资源,如果没有,就交给静态资源处理器,静态资源处理器也没有找到,则返回404

静态资源访问前缀
默认无前缀,如果想指定静态资源前缀,可以 通过spring.mvc.static-path-pattern配置

image-20240811095010981

请求路径上通过 项目上下文路径+静态资源前缀+静态资源名的方式访问

image-20240811100227511

指定静态资源位置

静态资源默认存放的路径为static content from a directory called /static (or /public or /resources or /META-INF/resources)

可以通过

image-20240811100321610

static-locations 指定专门的静态资源路径

image-20240811100338612

springboot还支持静态资源webjars 的处理方式,就是将静态资源打成jar导入

1
https://www.webjars.org/ 

项目中导入jQuery依赖

1
2
3
4
5
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>

导入后,自动映射

image-20240811100920711

1
http://localhost:8080/springboot-static-content/webjars/jquery/3.6.0/jquery.js

欢迎页功能

方式1 支持静态资源欢迎页, index.html 注意此时不能配置静态资源前缀,否则不生效
静态资源位置

image-20240811101314698

方式2 通过controller ,定义 / 和/index映射 路径

1
2
3
4
@RequestMapping(path={"/","/index"})
public String welcome(){
return "welcome";
}

导入thymeleaf

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.4.5</version>
</dependency>

定义Favicon

image-20240811101520985

static目录下准备一个favicon.ico的一个图片即可

image-20240811101544166

浏览器默认访问favicon.ico的路径为 协议://ip:端口号/favicon.ico,所以这里不要设置项目的上下文路径

拦截器静态资源放行

定义一个登陆页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link type="text/css" href="css/mycss.css">
<script src="js/myjs.js"></script>
</head>
<body>
<img src="img/logo.png">
<form action="login">
<input type="text" name="username" >
<input type="submit">
</form>
</body>
</html>

定义一个登陆controller

1
2
3
4
5
6
7
8
9
10
11
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(String username, HttpServletRequest request){
if(null != username&&!"".equals(username)){
request.getSession().setAttribute("username", username);
return "main";
}
return "redirect:/login.html";
}
}

定义一个登陆拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object username = request.getSession().getAttribute("username");
// 如果登录过,那么就放行
if(null != username){
return true;
}
// 如果没登陆过,那么就回到登录页,重定向
response.sendRedirect("login.html");
return false;
}
}

配置拦截器

1
2
3
4
5
6
7
8
9
10
@Configuration
public class MyInterceptorRegist implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
//配置拦截器的映射
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login","/login.html","/css/**","/js/**","/img/**","/font/**");
}
}

springboot_文件上传

创建项目:略

启动文件上传服务器:

1、新建个tomcat作为文件上传服务器,

设置远程服务器端口号

image-20240811143600080

image-20240811143547115

webapps下创建一个upload目录

image-20240811143614594

2、导入依赖

1
2
3
4
5
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.19</version>
</dependency>

3、页面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.progress {
width: 200px;
height: 10px;
border: 1px solid #ccc;
border-radius: 10px;
margin: 10px 0px;
overflow: hidden;
}
/* 初始状态设置进度条宽度为0px */
.progress > div {
width: 0px;
height: 100%;
background-color: yellowgreen;
transition: all .3s ease;
}
</style>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$("#uploadFile").click(function(){
// 获取要上传的文件
var photoFile =$("#photo")[0].files[0]
if(photoFile==undefined){
alert("您还未选中文件")
return;
}
// 将文件装入FormData对象
var formData =new FormData();
formData.append("headPhoto",photoFile)
// ajax向后台发送文件
$.ajax({
type:"post",
data:formData,
url:"file/upload",
processData:false,
contentType:false,
success:function(result){
// 接收后台响应的信息
alert(result.message)
// 图片回显
$("#headImg").attr("src",result.newFileName);
},
xhr: function() {
var xhr = new XMLHttpRequest();
//使用XMLHttpRequest.upload监听上传过程,注册progress事件,打印回调函数中的event事件
xhr.upload.addEventListener('progress', function (e) {
//loaded代表上传了多少
//total代表总数为多少
var progressRate = (e.loaded / e.total) * 100 + '%';
//通过设置进度条的宽度达到效果
$('.progress > div').css('width', progressRate);
})
return xhr;
}
})
})
})
</script>
</head>
<body>
<form action="addPlayer" method="get">
<p>账号<input type="text" name="name"></p>
<p>密码<input type="text" name="password"></p>
<p>昵称<input type="text" name="nickname"></p>
<p>头像:
<br/>
<input id="photo" type="file">
<br/>
<img id="headImg" style="width: 200px;height: 200px" alt="你还未上传图片">
<br/>
<div class="progress">
<div></div>
</div>
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
</p>
<p><input type="submit" value="注册"></p>
</form>
</body>
</html>

5、Controller代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Controller
@RequestMapping("/file")
public class FileController {
// 文件存储位置
private final static String FILESERVER="http://127.0.0.1:8090/upload/";
@RequestMapping("/upload")
@ResponseBody
public Map<String,String> upload(MultipartFile headPhoto, HttpServletRequest req) throws IOException {
Map<String,String> map=new HashMap<>();
// 指定文件存储目录为我们项目部署环境下的upload目录
String realPath = req.getServletContext().getRealPath("/upload");
File dir = new File(realPath);
// 如果不存在则创建目录
if(!dir.exists()){
dir.mkdirs();
}
// 获取文件名
String originalFilename = headPhoto.getOriginalFilename();
// 避免文件名冲突,使用UUID替换文件名
String uuid = UUID.randomUUID().toString();
// 获取拓展名
String extendsName = originalFilename.substring(originalFilename.lastIndexOf("."));
// 新的文件名
String newFileName=uuid.concat(extendsName);
// 创建 sun公司提供的jersey包中的client对象
Client client=Client.create();
WebResource resource = client.resource(FILESERVER + newFileName);
// 文件保存到另一个服务器上去了
resource.put(String.class, headPhoto.getBytes());
// 上传成功之后,把文件的名字和文件的类型返回给浏览器
map.put("message", "上传成功");
map.put("newFileName", FILESERVER+newFileName);
map.put("filetype", headPhoto.getContentType());
return map;
}
}

yml中配置文件大小限制

1
2
3
4
5
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB

多文件同步上传处理方式

页面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<form action="file/upload" method="post" enctype="multipart/form-data">
<p>账号<input type="text" name="name"></p>
<p>密码<input type="text" name="password"></p>
<p>昵称<input type="text" name="nickname"></p>
<p>头像:
<br/>
<input id="photo" name="photo" type="file">
<input id="photos" name="photos" type="file" multiple>
<br/>
<img id="headImg" style="width: 200px;height: 200px" alt="你还未上传图片">
<br/>
<div class="progress">
<div></div>
</div>
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
</p>
<p><input type="submit" value="注册"></p>
</form>

后台接收的处理单元参数处理

1
2
3
4
5
public Map<String,String> upload(String name,
String password,
String nickname,
@RequestPart("photo") MultipartFile photo,
@RequestPart("photos") MultipartFile[] photos, HttpServletRequest req)

springboot_Mybatis-plus

使用步骤

1、导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>

自动配置的内容
MyBatis PlusAutoConfiguration配置类,MyBatisPlusProperties配置项前缀 mybatis-plus: *就是对mybatis-plus的参数的设置

SQLSessionFactory已经配置好
mapperlocations 自动配置好的,默认值是classpath:/mapper/**/.xml 意为任意包路径下所有的mapper包下的xml文件

@Mapper建议替换成MapperScan

在yaml中配置mybatis-plus

1
2
mybatis-plus:
type-aliases-package: com.bitzh.pojo

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@AllArgsConstructor
@NoArgsConstructor
@Data
@TableName("dept")
public class Dept implements Serializable {
//如果数据库里面没有aaa就不会报错
/*@TableField(exist = false)
private String aaa;*/
//数据库的字段名不一致的匹配方法
@TableField("deptno")
private Integer deptno;
private String dname;
private String loc;
}

mapper(这里在启动类上加了@MapperScan)

1
2
3
4
public interface DeptMapper  extends BaseMapper<Dept> {

}

service
接口

1
2
3
4
//这个IService是mybatis-plus提前准备好的了
//基础的增删改查已经封装好了
public interface DeptService extends IService<Dept> {
}

实现类

1
2
3
@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {
}

在测试类中测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@SpringBootTest
class SpringbootMybatisplusApplicationTests {
@Autowired
DeptService deptService;
@Test
public void testFindAll(){
List<Dept> list = deptService.list();
for (Dept dept : list) {
System.out.println(dept);
}
}
// 查询集合
@Test
public void testQueryWrapper(){
// 部门号》=20
// QueryWrapper 作用就是在原本的SQL语句后面拼接where条件
// selec * from where delete from dept where update dept set ... where ....
QueryWrapper<Dept> queryWrapper=new QueryWrapper<>();
//queryWrapper.ge("deptno", 20).eq("dname", "ACCOUNTING").likeRight("dname", "A");
//queryWrapper.likeRight("dname", "A");
List<Dept> list = deptService.list(queryWrapper);
for (Dept dept : list) {
System.out.println(dept);
}
}
// 查询单个
@Test
public void testQueryWrapper2(){
QueryWrapper<Dept> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("deptno", 20);
Dept dept = deptService.getOne(queryWrapper);
System.out.println(dept);
}
// 增加
@Test
public void testAdd(){
boolean save = deptService.save(new Dept(null, "aaa", "bbb"));
System.out.println(save);
}
// 修改
@Test
public void testUpdate(){
// 要更新的数据
Dept dept =new Dept();
dept.setDname("xxx");
dept.setLoc("yyy");
// 更新的条件
QueryWrapper<Dept> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("deptno", 41);
boolean update = deptService.update(dept, queryWrapper);
System.out.println(update);
}
// 删除
@Test
public void testRemove(){
QueryWrapper<Dept> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("deptno", 41);
boolean remove = deptService.remove(queryWrapper);
System.out.println(remove);
}
}

分页插件的使用
配置分页插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor =new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor =new PaginationInnerInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
//paginationInnerInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
//paginationInnerInterceptor.setMaxLimit(500L);
// 设置数据库类型
paginationInnerInterceptor.setDbType(DbType.MYSQL);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}

测试分页插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testPage(){
// 当前页 页大小
QueryWrapper<Dept> queryWrapper=new QueryWrapper<>();
//queryWrapper.likeRight("dname", "A");
Page<Dept> page = deptService.page(new Page<>(1, 2), queryWrapper);
// 当前页数据 总页数 总记录数 当前页 页大小 ... ..
List<Dept> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("总页数:"+page.getPages());
System.out.println("总记录数:"+page.getTotal());
System.out.println("当前页:"+page.getCurrent());
System.out.println("页大小:"+page.getSize());
}

springboot_junit5

springboot 2.2.0开始引入Junit5作为单元测试的默认库
JUnit5和之前的版本有很大的不同,由单个子项目的几个不同模块组成
JUnit Platform ,是在JVM上启动测试框架的技术,不仅支持Junit自己的测试引擎,其他的测试引擎也可以
JUnit Jupiter,提供了Junit5的最新的编程模型,是Junit5 的核心,内部包含了一个测试引擎,用于在Junit Platform上运行
JUnit Vintager: 提供了兼容Junit4/3 的测试引擎

Junit5 = JUnit Platform+ JUnit Jupiter+JUnit Vintager

Junit支持Spring中的注解,测试起来比较方便, @Autowired @Transactional 等

image-20240811160057314

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@SpringBootTest // 使用springboot的容器功能
/*@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith({SpringExtension.class})*/
@DisplayName("Junit5测试类")// 测试类描述
class SpringbootMybatisplusApplicationTests2 {
@Autowired
private DeptMapper deptMapper;
@BeforeEach
public void testForeach(){
System.out.println("beforeach");
}
@AfterEach
public void testAftereach(){
System.out.println("aferEach");
}
@BeforeAll
public static void beforeAll(){
System.out.println("beforall");
}
@AfterAll
public static void aferAll(){
System.out.println("afterAll");
}
@RepeatedTest(3)// 重复测试3次
@Timeout(value = 10000,unit = TimeUnit.MILLISECONDS)// 超时时间设置
@DisplayName("Junit测试方法1")
@Test
public void test1(){
System.out.println("a");
System.out.println(deptMapper);
}
@Disabled// 设置不可用
@DisplayName("Junit测试方法2") // 方法描述
@Test
public void test2(){
System.out.println("b");
}
}

断言机制
断定某件事情,一定会发生,如果没有发生,那就是出现了问题,所欲的测试运行结束后,会有一个详细的断言报告
用来对测试需要满足的条件进行验证,这些断言方法都是org.junit.jupiter.api.Assertions中的静态方法,
简单断言

image-20240811161204276

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@SpringBootTest
@DisplayName("Junit5断言测试类")
class SpringbootMybatisplusApplicationTests3 {
@DisplayName("简单断言1")
@Test
public void testAssertions1(){
int add = add(1, 2);
Assertions.assertEquals(6,add,"add结果计算错误");
}
public int add(int a,int b){
return a+b;
}
@DisplayName("简单断言2")
@Test
public void testAssertions2(){
String s =new String("xxx");
String s2=new String("abc");
Assertions.assertEquals(s,s2,"String对象不一样");
}
// 组合断言
@DisplayName("组合断言")
@Test
public void testAssertAll(){
Assertions.assertAll("AssertAll",
()-> Assertions.assertTrue(true&& false),
()-> Assertions.assertEquals(1,2));
}
// 异常断言 认为应该会出现异常
@DisplayName("异常断言")
@Test
public void testAssertException(){
Assertions.assertThrows(ArithmeticException.class, ()->{ int i=1/0;}, "没有抛出异常");
}
// 超时断言 判断有没有超时
@DisplayName("超时断言")
@Test
public void testAssertTimeOut(){
Assertions.assertTimeout(Duration.ofMillis(1000),()-> Thread.sleep(5000));
}
// 快速失败
@DisplayName("快速失败")
@Test
public void testFail(){
if(true){
Assertions.fail("测试 失败");
}
}
}

前置条件(assumptions假设)
类似于断言,不同在于,不满足断言回事方法测试失败,而不满足的前置条件会使得的是方法的执行中止,前置条件可以看成是测试方法执行的前提,当条件不满足时,就没有继续执行的必要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@SpringBootTest
@DisplayName("Junit5测试前置条件")
class SpringbootMybatisplusApplicationTests4 {
@DisplayName("测试前提条件")
@Test
public void testAssumptions(){
// 假设为true,才会执行
Assumptions.assumeTrue(false,"结果不是true");
System.out.println("后面的测试代码前提条件");
}
@DisplayName("简单断言1")
@Test
public void testAssertions1(){
int add =10;
Assertions.assertEquals(6,add,"add结果计算错误");
System.out.println("后面的测试代码简单断言");
}
}

嵌套测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@DisplayName("嵌套测试")
class SpringbootMybatisplusApplicationTests5 {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
// 外层的测试不能驱动内层的测试方法
assertNull(stack);
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach // 内层Test可以驱动外层的BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}

参数化测试

image-20240811162658346

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@DisplayName("参数化测试")
class SpringbootMybatisplusApplicationTests6 {
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
System.out.println(argument);
assertTrue(argument > 0 && argument < 4);
}
@ParameterizedTest
@MethodSource("stringProvider")
void testWithExplicitLocalMethodSource(String argument) {
assertNotNull(argument);
}
static Stream<String> stringProvider() {
return Stream.of("apple", "banana");
}
}