1. SSM环境搭建回顾

  • spring
  • springmvc
  • mybatis
  • spring springmvc mybatis 简单功能 员工添加 查询

image-20240324205956681

1.1 pom.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
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
94
95
96
97
98
99
100
101
102
103
104
105
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.6</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.6</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.6</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.6</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.6</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.6</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.6</version>
</dependency>

<!-- springmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.6</version>
</dependency>

<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>

<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>

<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>

<!-- mybatis-spring-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>

<!-- jackson 转换json-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.3</version>
</dependency>

<!-- aspectj aop切面 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>

<!--aspectj-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>

注意:spring相关依赖要保证版本号一致

1.2 编写spring.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
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--开启注解扫描-->
<context:component-scan base-package="com.baizhi.service"/>

<!--创建数据源-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>

<!--创建sqlSessionFactory-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<property name="dataSource" ref="dataSource"/>
<!--mapper配置文件位置-->
<property name="mapperLocations" value="classpath:com/baizhi/mapper/*.xml"/>
<!--实体别名 类名或类名小写均可 -->
<property name="typeAliasesPackage" value="com.baizhi.entity"/>
</bean>

<!--创建DAO-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.baizhi.dao"/>
</bean>

<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!--开启注解事务生效 @Transactional-->
<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

1.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
package com.baizhi.entity;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.util.Date;

public class Emp {

private Integer id;
private String name;
@JsonFormat(pattern = "yyyy-MM-dd")//json格式日期转换,只对日期生效
private Date birthday;
private Double salary;

public Emp() {
}

public Emp(Integer id, String name, Date birthday, Double salary) {
this.id = id;
this.name = name;
this.birthday = birthday;
this.salary = salary;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public Double getSalary() {
return salary;
}

public void setSalary(Double salary) {
this.salary = salary;
}

@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + '\'' +
", birthday=" + birthday +
", salary=" + salary +
'}';
}
}

1.4 开发DAO组件

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

import com.baizhi.entity.Emp;

import java.util.List;

public interface EmpDAO {

//保存
void save(Emp emp);

//查询所有
List<Emp> findAll();
}

1.5 开发Mapper配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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.baizhi.dao.EmpDAO">

<!--保存
useGeneratedKeys="true" keyProperty="id"仅对mysql数据库有效,
可进行主键自增,插入数据时直接插入null值即可
-->
<insert id="save" parameterType="Emp" useGeneratedKeys="true" keyProperty="id">
insert into emp
values (#{id}, #{name}, #{birthday}, #{salary})
</insert>

<!--查询所有-->
<select id="findAll" resultType="Emp">
select id,name,birthday,salary from emp
</select>

</mapper>

1.6 开发Service组件

1
2
3
4
5
6
7
8
9
10
11
12
package com.baizhi.service;

import com.baizhi.entity.Emp;

import java.util.List;

public interface EmpService {

void save(Emp emp);

List<Emp> findAll();
}

1.7 编写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
package com.baizhi.service;

import com.baizhi.dao.EmpDAO;
import com.baizhi.entity.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service("empService")
@Transactional
public class EmpServiceImpl implements EmpService{
private EmpDAO empDAO;
//现在spring推荐构造注入
@Autowired
public EmpServiceImpl(EmpDAO empDAO) {
this.empDAO = empDAO;
}

@Override
public void save(Emp emp) {
empDAO.save(emp);
}

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

1.8 测试Service组件

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

import com.baizhi.entity.Emp;
import com.baizhi.service.EmpService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.Date;

public class TestEmpServiceImpl {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
EmpService empService = (EmpService) context.getBean("empService");
//整体赋值用构造器更快,单个可用set方法
empService.save(new Emp(null,"皮卡丘",new Date(),123.456));
empService.findAll().forEach(emp -> System.out.println("emp = " + emp));
//输出结果:emp = Emp{id=1, name='皮卡丘', birthday=Wed Apr 27 16:53:24 CST 2022, salary=123.46}
}

1.9 编写springmvc.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
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!--开启注解扫描,springmvc是子容器我们扫描controller即可-->
<context:component-scan base-package="com.baizhi.controller"/>

<!--开启mvc注解驱动-->
<mvc:annotation-driven/>

<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>

</beans>

1.10 配置web.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
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Archetype Created Web Application</display-name>


<!-- 加载spring.xml-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>

<!-- 配置spring工厂启动,随着服务器启动这个类一旦启动就会读上边键 相当于1.8中启动工厂,dao,service我们就有了-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 配置springmvc controller也有了-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<!-- 配置servlet映射-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

1.11 开发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.baizhi.controller;

import com.baizhi.entity.Emp;
import com.baizhi.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController //使用此注解可将类中返回值转为json响应到浏览器
/**
* @RestController@Controller之间的区别:
* 如果在类上加上@RestController,该类中所有SpringMVC接口映射都是返回json格式,相当于在每个方法上加上@ResponseBody注解,
* @RestController是我们SpringMVC提供的,而不是Springboot提供,
* Rest微服务接口开发中 Rest风格 数据传输格式json格式 协议http协议,
* Controller 控制层注解 SpringMVCUrl接口映射 默认情况下返回页面跳转 如果需要返回json格式的情况下需要@ResponseBody注 * 解。
*/
@RequestMapping("emp")
public class EmpController {

private EmpService empService;
//推荐构造注入
@Autowired
public EmpController(EmpService empService) {
this.empService = empService;
}

//保存
@RequestMapping("save")
public void save(Emp emp){
empService.save(emp);
}

//查询所有
@RequestMapping("findAll")
public List<Emp> findAll(){
return empService.findAll();
}
}

1.12 部署tomcat服务器进行测试

测试插入数据:

访问地址:http://localhost:8888/ssm/emp/save?name=猪猪侠&birthday=2020/12/12&salary=234.567

测试查询数据:

访问地址:http://localhost:8888/ssm/emp/findAll

2.现有SSM开发存在问题

  • 大量maven冗余配置
  • 每次构建项目都要书写大量相同配置极大浪费了项目开发时间
  • 每次整合第三方技术都需要编写相关配置文件
  • 项目测试每次都需要部署到tomcat

注意:这就是早期的SSM或者SSH开发存在问题,是不是很麻烦

3. SpringBoot的引言

  Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来**简化**Spring应用的**初始搭建**以及**开发过程**。该框架使用了**特定的方式来进行配置**,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

Spring Boot 全新框架作用:简化spring应用初始搭建和开发过程

如何简化:开发人员使用springboot只要基于特定方式进行配置,简化spring使用

SpringBoot 微框架: 5分钟完成之前ssm中环境

springboot(微框架) = springmvc(控制器) + spring core(项目管理)

即:只要我们搭建了springboot环境就相当于我的项目中有了spring和springmvc环境

4. SpringBoot的优势

(1)创建完整的独立的Spring应用程序:传统ssm框架中是有父子容器概念的,父子容器是一定不能污染的(父容器扫到子容器,子容器扫到父容器),现在使spring、springmvc只有一个容器

(2)内部嵌入了Tomcat,无需部署WAR文件:springboot内嵌tomcat,应用跑在内嵌服务器

(3)简化Maven配置,自动配置Spring、Springmvc,没有XML配置:只需要引入少数几个依赖

“用了springboot,spring应用再无xml”

5. SpringBoot的项目约定

img

img

总结:

img

  • springboot项目中必须在src/main/resources中放入application.yml(.properties)核心配置文件,且名字必须为:application

  • springboot项目中必须在src/main/java中所有子包之外构建全局入口类型:xxApplication,入口类一个springboot项目只能有一个

6. SpringBoot环境搭建

SpringBoot官网:Spring Boot

img

新建项目时只需要建立普通的maven项目即可,不需要勾选任何选项:

image-20240324231737312

image-20240324231747789

新建后的项目结构如下,需要额外引入src/test/resources目录:

image-20240324231812093

6.1 环境要求

1
2
3
4
5
6
7
8
9
10
# 1.System Requirements
JDK 1.8+
MAVEN 3.2+
Spring Framework 5.x+ 即SpringBoot 2.5.0以上

# 2.ServletContainers:
Tomcat 9.0+

# 3.开发工具
IDEA 2021版本

6.2 新建项目中引入依赖pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--
继承springboot的父项目,便于维护版本 即以后再写springboot组下的依赖不用再写版本了,都沿用父项目的版本
2.3之前版本:2.3.x.RELEASE
2.4之后版本:2.4.x 去掉了RELEASE,改为纯数字表示
-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>

<!--引入依赖-->
<dependencies>
<!--引入springboot的web支持spring-boot-starter-web 一个依赖即可引入以前“spring”所有相关依赖-==》传导性依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

6.3 resources目录下引入application.yml配置文件

1
2
3
4
5
# 公共配置
server:
port: 8888 # 修改内嵌服务器端口号
servlet:
context-path: /springboot01 # 修改项目名 注意项目名必须以“/”开头 访问路径要拼上

6.4 编写入口类,启动项目

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
//在项目中如下的包结构中创建入口类 Application
/*
com
+| study
*/
package com.study;

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

/**
* @ClassName SpringBoot01Application
* @Description TODO
* @Author chabai
* @Date 2022/4/28 11:27
* @Version 1.0
*/

/**
* @SpringBootApplication注解
* 修饰范围:只能用在入口类上,且只能出现一次
* 作用:标识这个类是一个springboot的入口类,是启动整个springboot项目的总入口
* springboot项目创建步骤总结:
* 1.pom.xml文件引入依赖
* 2.resources目录下生成application.yml
* 3.创建入口类加入@SpringBootApplication注解,在main中启动应用
*/
@SpringBootApplication
public class SpringBoot01Application {
public static void main(String[] args) {
/**
* 启动springboot应用
* 参数1:指定入口类的类对象(.class),注意不是类的对象(new xxx)
* 参数2:main函数参数
* 注意:默认端口是8080,如果端口被占用,需要在application.yml中重新指定端口,否则不能正常启动项目
*/
SpringApplication.run(SpringBoot01Application.class,args);
}
}
// springboot = springmvc(控制器controller) + spring(工厂) 即springboot一旦启动这两个环境都有了
/**
* @SpringBootApplication: 注解
* 组合注解: 就是由多个注解组合而成一个注解
* 元注解 : 用来修饰注解的注解,如:@Target@Retention@Documented@Inherited
* @Target: 指定注解作用范围
* @Retention: 指定注解什么时候有效
* 包含下面三个注解:
* @SpringBootConfiguration:
* 这个注解就是用来自动配置spring、springmvc(初始化servlet ...)相关环境
*
* @EnableAutoConfiguration: 开启自动配置
* 自动配置核心注解 自动配置spring相关环境 自动与项目中引入的第三方技术自动配置其环境
* mybatis-springboot、redis-springboot 、es-springboot 、rabbitmq 第三方技术
*
* @ComponentScan: 组件扫描
* 根据注解发挥注解作用,默认扫描当前包及其子包
*
* 启动springboot应用时候需要传递main函数参数作为启动的第二个参数:
* 主要用途是测试用,项目启动后动态传参,传递JVM相关的一些参数
*
* 外部部署时打包成jar包:java -jar --spring.config.location=绝对路径 xxx.jar
*
*/

运行main启动项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.0)

2022-04-28 11:29:21.768 INFO 10908 --- [ main] com.study.SpringBoot01Application : Starting SpringBoot01Application using Java 1.8.0_131 on cjn-PC with PID 10908 (D:\Software_Development\IDEA_code\SpringBoot\springboot01\target\classes started by cjn in D:\Software_Development\IDEA_code\SpringBoot\springboot01)
2022-04-28 11:29:21.775 INFO 10908 --- [ main] com.study.SpringBoot01Application : No active profile set, falling back to default profiles: default
2022-04-28 11:29:22.628 INFO 10908 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8888 (http)
2022-04-28 11:29:22.642 INFO 10908 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-04-28 11:29:22.642 INFO 10908 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.46]
2022-04-28 11:29:22.716 INFO 10908 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-04-28 11:29:22.716 INFO 10908 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 895 ms
2022-04-28 11:29:23.044 INFO 10908 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8888 (http) with context path ''
2022-04-28 11:29:23.053 INFO 10908 --- [ main] com.study.SpringBoot01Application : Started SpringBoot01Application in 1.805 seconds (JVM running for 3.3)
2022-04-28 11:29:23.054 INFO 10908 --- [ main] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to CORRECT
2022-04-28 11:29:23.056 INFO 10908 --- [ main] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
//说明: 出现以上日志说明启动成功

注意:到这里项目环境已经搭建成功了

6.5 创建控制器并测试

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
//在项目中创建指定的包结构
/*
com
+| study
+| controller */
@Controller
package com.study.controller;

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

/**
* @ClassName HelloController
* @Description TODO
* @Author chabai
* @Date 2022/4/28 12:31
* @Version 1.0
*/
@RestController
@RequestMapping("hello")
public class HelloController {
/**
* @MethodName test
* @Description 测试控制器:测试是否有spring和springmvc环境
* 访问地址:http://localhost:端口号/项目名/请求路径
* 注意:springboot项目默认启动没有项目名,例如:http://localhost:8888/hello/test
* 需要指定项目名时,要在application.yml里面进行配置
* @return: java.lang.String
* @Author chabai
* @Date 2022/4/28 12:32
*/
@RequestMapping("test")
public String test(){
System.out.println("hello springboot!");
return "Hello SpringBoot!";
}
}

启动项目进行测试控制器:

1
2
3
4
5
# 注意: springboot的项目启动默认无项目名,此处指定了项目名
- 访问路径: http://localhost:8888/springboot01/hello/test

# 注意: springboot的项目启动默认无项目名
- 访问路径: http://localhost:8888/hello/test

测试结果:

(1)配置项目名

image-20240324232430967

(2)不配置项目名

image-20240324232439293

与此同时,控制台输出:hello springboot!

7.相关注解说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# springboot =  springmvc(控制器controller) +  spring(工厂)

# 入口类 SpringApplication
- SpringBootApplication: 全局入口类 有且只能有一个

# @SpringBootApplication: 注解
组合注解: 就是由多个注解组合而成一个注解
元注解 : 用来修饰注解的注解,如:@Target、@Retention、@Documented、@Inherited
@Target: 指定注解作用范围
@Retention: 指定注解什么时候有效
包含下面三个注解:
# @SpringBootConfiguration:
这个注解就是用来自动配置spring、springmvc(初始化servlet ...)相关环境

# @EnableAutoConfiguration: 开启自动配置
自动配置核心注解 自动配置spring相关环境 自动与项目中引入的第三方技术自动配置其环境
mybatis-springboot、redis-springboot 、es-springboot 、rabbitmq 第三方技术

# @ComponentScan: 组件扫描
根据注解发挥注解作用,默认扫描当前包及其子包

# 注意启动springboot应用时候需要传递main函数参数作为启动的第二个参数,它的主要作用是:
测试用,项目启动后动态传参,在启动时指定jvm参数覆盖默认配置

8.配置文件

8.1 配置文件的拆分

 说明:在实际开发过程中生产环境和测试环境有可能是不一样的,因此将生产中的配置和测试中的配置拆分开是非常必要的,在springboot中也提供了配置文件拆分的方式。

img

这里以生产中项名名称不一致为例:

  • 生产中项目名为: springboot_prod
  • 测试中项目名为: springboot_dev
  • 端口同时为: 8888(也可设置不同)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 主配置文件 application.yml:用来书写相同的的配置
# 公共配置
server:
port: 8888 # 修改内嵌服务器端口号
# servlet:
# context-path: /springboot01 # 修改项目名 注意项目名必须以“/”开头

# 激活环境配置,指定哪个环境配置生效(此处写配置文件名的简写)
spring:
profiles:
# active: prod # 让prod生效
active: dev # 让dev生效


# 生产配置文件 application-pord.yml,其中prod为生产配置文件简名
server:
servlet:
context-path: /springboot_prod


# 测试配置文件 application-dev.yml,其中dev为测试配置文件简名
server:
servlet:
context-path: /springboot_dev

image-20240324232706507

启动项目后测试:

生产访问路径:http://localhost:8888/springboot_prod/hello/test

测试访问路径:http://localhost:8888/springboot_dev/hello/test

控制台同时输出hello springboot!

8.2 启动指定配置文件

  说明:往往在企业级开发过程中为SpringBoot应用启动时指定一个完整外部配置也是经常用到的,在SpringBoot中也提供这个方式来启动项目,如下所示:

image-20240324232820242

application_local_prod.yml配置文件代码

1
2
3
4
5
# 公共配置
server:
port: 9999 # 修改内嵌服务器端口号
servlet:
context-path: /springboot_local_prod

需要额外进行配置:

1
2
3
4
5
6
7
8
9
Program arguments:需要指定绝对路径

例如:

--spring.config.location=D:\Software_Development\IDEA_code\SpringBoot\springboot01\application-local-prod.yml

复制application_local_prod.yml路径方法:选中文件---右键---Copy---Absolute Path

外部部署时打包成jar包:java -jar --spring.config.location=绝对路径 xxx.jar

image-20240324232918778

启动项目后测试:访问路径:http://localhost:9999/springboot_local_prod/hello/test

控制台同时输出hello springboot!

9.创建项目的两种方式

9.1 方式一:基于maven

File—New—Module—Maven—可以不选任何骨架 / 可以勾选maven-archetype-quickstart / 可以勾选maven-archetype-webapp,完成项目创建后需要额外添加test目录下的resources目录。

image-20240324233043840

image-20240324233052207

9.2 方式二:基于Spring Initializr快速初始化(推荐)

File—New—Module—Spring Initializr—选择SDK和starter service URL

image-20240324233109767

设置Group、Artifict、Type、Packaging、Java Version等参数

image-20240324233124510

选择依赖

image-20240324233147445

创建后的项目结构如下:

image-20240324233220737

通过Spring Initializr创建的项目结构完整,生成的其他文件说明如下:

1、创建项目时,建议各字段间以-分割,例如:spring-boot-day02

2、.mvn属于隐藏文件,主要用于配合mvnw(Linux)、mvnw.cmd(Windows)进行项目启动

3、.gitIgnore属于Git忽略文件配置文件,用于Git使用

4、Spring Initializr创建项目完成后默认配置文件为application.properties,建议修改为.yml后缀,即:application.yml,相比于properties,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
<!--springboot父项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
<!--引入web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--引入test测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<!--
打包插件,可以以java -jar打包
在打成jar包运行时,必须放入此插件配置,没有插件配置无法运行打包的项目
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

9.3 添加配置

1
2
3
4
server:
port: 8888 # 修改内置服务器访问端口
servlet:
context-path: /spring-boot-02

9.4 新建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.study.controller;

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

/**
* @ClassName HelloController
* @Description TODO
* @Author chabai
* @Date 2022/4/29 10:00
* @Version 1.0
*/
@RestController
@RequestMapping("hello")
public class HelloController {
//测试路径:http://localhost:8888/spring-boot-02/hello/test
@RequestMapping("test")
public String test(){
System.out.println("Hello SpringBoot!");
return "Hi SpringBoot!";
}
}

测试结果:

image-20240324233453328

控制台输出:Hello SpringBoot!

10.管理工厂对象的创建

10.0回顾spring工厂创建对象

  • img

10.1springboot创建对象

image-20240403124254051

10.1 .1创建单个对象

在springboot中管理单个对象可以直接使用原始spring框架中注解形式创建

  • @Component 通用的对象创建注解
    • @Controller 用来创建控制器对象,直接写在控制器类上
    • @Service 用来创建业务层对象,一般写在Service接口的对应实现类上
    • @Repository 用来创建DAO层对象,此注解一般很少用,DAO接口的实现类在mapper映射文件中实现
      • 以上注解都有value属性,value属性用来指定工厂中对象名称,默认名称为类名首字母小写,使用其它的名称时需重新设定,单个属性时可省略value,直接在“”中写对象名称。
1
2
3
4
@Service
public class DemoServiceImpl implements UserService{
//doing....
}

通过工厂创建之后可以在使用处注入该对象:

1
2
3
4
5
6
7
@Controller
@RequestMapping("hello")
public class HelloController {
@Autowired
private DemoService demoService;
//doing...
}

10.1.2 创建多个对象

如何在springboot中像spring框架一样通过xml创建多个对象?在SpringBoot中也提供了相同注解如**@Configuration + @Bean注解进行创建**

  • @Configuration 代修饰范围:用在类上 表示这是一个spring的配置类,相当于spring.xml配置文件

  • @Bean 用来在工厂中创建这个@Bean注解标识的对象:相当于 spring.xml书写bean标签

    • 默认使用@Bean创建对象在工厂中的唯一标识为方法名称(即复杂对象名称首字母小写作为方法名)
    • 修改工厂中对象的唯一标识可以在@Bean(“工厂中名字”)中的“”内重新指定一个名

(1)管理复杂对象的创建

注:抽象类不能直接实例化,一般通过抽象类内部提供的静态方法实例化

不交给工厂自己用

1
2
Calendar instance = Calendar.getInstance();
System.out.println("当前日期:"+instance.getTime());

交给工厂要建个包config放

1
2
3
4
5
6
7
8
9
10
11
12
13
  @Configuration //修饰范围:只能作用在类上,代表这是一个配置类,相当于spring.xml
public class Beans {
/**
* 修饰范围: 用在方法上或者注解上
* 作用: 用来将方法返回值交给工厂管理
* 方法名: 推荐返回值首字母小写,代表当前创建对象在工厂中名称
*/
@Bean
public Calendar calendar(){
return Calendar.getInstance();
}
}
//项目一启动springboot一扫到配置类,进而扫到bean就会自动创建Calendar

(2)使用复杂对象

1
2
3
4
5
6
7
8

@Controller
@RequestMapping("hello")
public class HelloController {
@Autowired
private Calendar calendar;//需要谁就把谁声明为成员变量,并加入相应的注入注解(需要这个要先存在)
......
}

注意:

1.@Configuration 用来在工厂中一次性创建多个对象,可同时创建简单对象和复杂对象

2.@Component 用来创建单个对象,一般在各层对应的类上使用

10.2 测试

开发BeanConfig

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
 
package com.study.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Calendar;

/**
* @ClassName BeanConfig
* @Description 用来进行复杂对象创建
* @Author chabai
* @Date 2022/4/29 12:14
* @Version 1.0
*/
@Configuration //修饰范围:只能作用在类上,代表这是一个配置类,相当于spring.xml
public class BeanConfig {
/**
* 修饰范围: 用在方法上或者注解上
* 作用: 用来将方法返回值交给工厂管理
* 方法名: 推荐返回值首字母小写,代表当前创建对象在工厂中名称
* @return
*/
@Bean
public Calendar calendar(){
return Calendar.getInstance();
}
}

开发Service

1
2
3
4
5
6
7
8
9
10
11
12
package com.study.service;

/**
* @ClassName DemoService
* @Description TODO
* @Author chabai
* @Date 2022/4/29 12:05
* @Version 1.0
*/
public interface DemoService {
void demo();
}

编写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
package com.study.service;

import org.springframework.stereotype.Service;

/**
* @ClassName DemoServiceImpl
* @Description TODO
* @Author chabai
* @Date 2022/4/29 12:06
* @Version 1.0
*/
/**
* 修饰范围: 用在类上,每次只能创建一个简单对象
* 作用: 在工厂中创建对象
* 默认工厂中名称为类名首字母小写
* value属性: 用来指定当前创建对象在工厂中名称
*/
@Service(value = "demoService")//原始默认对象名为demoServiceImpl
public class DemoServiceImpl implements DemoService{
@Override
public void demo() {
System.out.println("DemoService is Ok");
}
}

开发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
41
42
43
package com.study.controller;

import com.study.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Calendar;

/**
* @ClassName DemoController
* @Description 用来测试对象创建
* @Author chabai
* @Date 2022/4/29 12:01
* @Version 1.0
*/
@RestController
@RequestMapping("demo")
public class DemoController {
@Autowired //按照类型注入
private DemoService demoService;

@Autowired
@Qualifier(value = "demoService")//作用:用来修改autowired默认不再根据类型注入,修改为根据名称注入
private DemoService demoService1;

@Autowired
@Qualifier(value = "calendar")
private Calendar calendar;

//测试路径:http://localhost:8888/spring-boot-02/demo/test
//测试结果:浏览器和控制台输出下面对应内容
@RequestMapping("test")
public String test(){
System.out.println("demo ok");
demoService.demo();
demoService1.demo();//两次获取到的是同一个对象,即默认对象的创建方式为单例模型
System.out.println("demoService==demoService1 : " + (demoService==demoService1));
System.out.println("工厂中获取日历对象,当前时间为" + calendar.getTime());
return "Demo Ok!";
}
}

启动项目访问测试路径进行测试:http://localhost:8888/[spring-boot](https://so.csdn.net/so/search?q=spring-boot&spm=1001.2101.3001.7020)-02/demo/test

image-20240403130945701

控制台输出结果:

demo ok
DemoService is Ok
DemoService is Ok
demoService==demoService1 : true
工厂中获取日历对象,当前时间为Fri Apr 29 12:20:00 CST 2022

11.对象注入

语法:需要谁就把谁声明为成员变量,并加入相应的注入注解

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.study.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Calendar;

/**
* @ClassName BeanConfig
* @Description 用来进行复杂对象创建
* @Author chabai
* @Date 2022/4/29 12:14
* @Version 1.0
*/
@Configuration //修饰范围:只能作用在类上,代表这是一个配置类,相当于spring.xml
public class BeanConfig {
/**
* 修饰范围: 用在方法上或者注解上
* 作用: 用来将方法返回值交给工厂管理
* 方法名: 推荐返回值首字母小写,代表当前创建对象在工厂中名称
* @return
*/
@Bean
public Calendar calendar(){
return Calendar.getInstance();
}
}

开发Service

1
2
3
4
5
6
7
8
9
10
11
12
package com.study.service;

/**
* @ClassName DemoService
* @Description TODO
* @Author chabai
* @Date 2022/4/29 12:05
* @Version 1.0
*/
public interface DemoService {
void demo();
}

编写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
package com.study.service;

import org.springframework.stereotype.Service;

/**
* @ClassName DemoServiceImpl
* @Description TODO
* @Author chabai
* @Date 2022/4/29 12:06
* @Version 1.0
*/
/**
* 修饰范围: 用在类上,每次只能创建一个简单对象
* 作用: 在工厂中创建对象
* 默认工厂中名称为类名首字母小写
* value属性: 用来指定当前创建对象在工厂中名称
*/
@Service(value = "demoService")//原始默认对象名为demoServiceImpl
public class DemoServiceImpl implements DemoService{
@Override
public void demo() {
System.out.println("DemoService is Ok");
}
}

开发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
41
42
43
package com.study.controller;

import com.study.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Calendar;

/**
* @ClassName DemoController
* @Description 用来测试对象创建
* @Author chabai
* @Date 2022/4/29 12:01
* @Version 1.0
*/
@RestController
@RequestMapping("demo")
public class DemoController {
@Autowired //按照类型注入
private DemoService demoService;

@Autowired //用来修改autowired默认不再根据类型注入,修改为根据名称注入
@Qualifier(value = "demoService")
private DemoService demoService1;

@Autowired
@Qualifier(value = "calendar")
private Calendar calendar;

//测试路径:http://localhost:8888/spring-boot-02/demo/test
//测试结果:浏览器和控制台输出下面对应内容
@RequestMapping("test")
public String test(){
System.out.println("demo ok");
demoService.demo();
demoService1.demo();//两次获取到的是同一个对象,即默认对象的创建方式为单例模型
System.out.println("demoService==demoService1 : " + (demoService==demoService1));
System.out.println("工厂中获取日历对象,当前时间为" + calendar.getTime());
return "Demo Ok!";
}
}

启动项目访问测试路径进行测试:http://localhost:8888/spring-boot-02/demo/test

image-20240324234256524

控制台输出结果:

1
2
3
4
5
demo ok
DemoService is Ok
DemoService is Ok
demoService==demoService1 : true
工厂中获取日历对象,当前时间为Fri Apr 29 12:20:00 CST 2022

12.属性注入

12.1 SpringBoot与spring属性注入方式对比

spring两种属性注入方式:引用类型:标签或者注解

​ 基本.. :标签或者注解

image-20240324235432383

springboot两种属性注入方式:八种基本类型+String+日期+数组+set list map:注解@Value

​ 注入我们的对象:注解@Autowired

image-20240324235440317

12.2基本属性注入

application.yml中给属性赋值,控制器中使用@Value注解完成属性注入

语法:需要谁就把谁声明为成员变量,并加入相应的注入注解

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 声明基本属性注入值
# 1.String+8种基本数据类型
name: 茶白
age: 26
weight: 144.60
birthday: 2012/12/12 12:12:12 # 注意:默认的日期格式为 yyyy/mm/dd HH:MM:ss
sex: true

# 2.数组
arrays: 1,2,3,4,5 # 注意:注入数组元素的时候,多个元素间使用“,”进行分割

# 3.集合
lists: pig,dog,cat,tiger,bee # 规则同数组一致
maps: "{'a':'熊大','b':'熊二','c':'光头强'}" # 注意:注入map集合要使用json形式进行注入,使用 @Value注入时必须加入 "#{${属性}}" 进行注入

InjectionController

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
package com.study.controller;

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

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
* @ClassName InjectionController
* @Description 用来测试属性注入
* @Author chabai
* @Date 2022/4/30 9:18
* @Version 1.0
*/
@RestController
@RequestMapping("inject")
public class InjectionController {
//1.测试注入String类型+8种基本类型
@Value("${name}")
private String name;

@Value("${age}" //@Value("xxx")可以不需要配置文件,直接赋值,但这种方法把值写死了不利于代码维护 还是推荐写到配置文件里 //使用spring el表达式去引用配置文件的值,这要就可以把配置文件的值注入属性中,实现属性和配置的解耦
private Integer age;

@Value("${weight}")
private Double weight;

@Value("${birthday}")
private Date birthday;

@Value("${sex}")
private Boolean sex;

//2.测试数组
@Value("${arrays}")
private String[] arrays;

//3.测试集合
@Value("${lists}")
private List<String> lists;

@Value("#{${maps}}") //注意:在注入map集合时,配置文件中要使用json格式,使用时必须使用"#{${xxx}}"进行获取
private Map<String,String> maps;


//测试路径:http://localhost:8888/spring-boot-02/inject/test
@RequestMapping("test")
public String test(){
System.out.println("inject ok");
System.out.println("name = " + name);
System.out.println("age = " + age);
System.out.println("weight = " + weight);
System.out.println("birthday = " + birthday);
System.out.println("sex = " + sex);
for (String array : arrays) {
System.out.println("array = " + array);
}
for (String list : lists) {
System.out.println("list = " + list);
}
maps.forEach((k,v)-> System.out.println("k = " + k + ", v = " + v));
return "Inject Ok";
}
}

启动服务测试,访问路径:http://localhost:8888/spring-boot-02/inject/test

image-20240324234807868

控制台输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
inject ok
name = 茶白
age = 26
weight = 144.6
birthday = Wed Dec 12 12:12:12 CST 2012
sex = true
array = 1
array = 2
array = 3
array = 4
array = 5
list = pig
list = dog
list = cat
list = tiger
list = bee
k = a, v = 熊大
k = b, v = 熊二
k = c, v = 光头强

12.3属性的对象方式注入(一次注入多个属性)

application.yml中给属性赋值,控制器中使用 @ConfigurationProperties(prefix=”前缀”) 注解完成属性注入,注意此种方式必须提供set方法

语法:需要谁就把谁声明为成员变量,并加入相应的注入注解

pom.xml中引入自定义注入元数据依赖

1
2
3
4
5
6
7
<!--这个依赖可以根据@ConfigurationProperties注解构建注入元数据-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<!--true代表这个依赖不会被(父子间)传递,仅当前项目可用-->
<optional>true</optional>
</dependency>

解决导入依赖后仍无提示的方法:

1.忘记刷新maven,重新刷新一下

2.重新build一下项目

3.clean一下maven

4.重启idea

application.yml

1
2
3
4
5
# 声明对象方式注入
orders:
id: 10
name: 红烧肉
price: 35.38

InjectionObjectController.java

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
package com.study.controller;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @ClassName InjectionObjectController
* @Description 测试对象注入
* @Author chabai
* @Date 2022/4/30 11:43
* @Version 1.0
*/
@RestController
@RequestMapping("injectObject")
/**
* @ConfigurationProperties 注解
* 修饰范围: 用来类上
* 作用: 用来指定前缀的属性,注入到当前对象中属性名一致的属性中
* 注意: 使用这个注解为属性一次性赋值,必须为属性提供set方法
*/
@ConfigurationProperties(prefix = "orders")
public class InjectionObjectController {
private Integer id;
private String name;
private Double price;

public void setId(Integer id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setPrice(Double price) {
this.price = price;
}

//测试路径:http://localhost:8888/spring-boot-02/injectObject/test
@RequestMapping("test")
public String test(){
System.out.println("injectObject ok");
System.out.println("id = " + id);
System.out.println("name = " + name);
System.out.println("price = " + price);
return "injectObject ok";
}
}

启动项目进行测试,访问路径:http://localhost:8888/spring-boot-02/injectObject/test

image-20240324235242290

控制台输出结果:

1
2
3
4
injectObject ok
id = 10
name = 红烧肉
price = 35.38

13.JSP模板集成

image-20240324235545437

在SpringBoot框架中默认模板推荐使用Thymeleaf模板,这里我们优先讲与JSP模板进行集成。

13.1pom.xml中引入jsp的集成jar包

1
2
3
4
5
6
7
8
9
10
11
12
<!--el表达式直接用即可,c标签库要引这个依赖-->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

<!--引入jsp解析依赖,让内嵌tomcat具有解析jsp功能,原来用的webtomcat默认有解析jsp功能,springboot内嵌的不具有-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>

13.2pom.xml引入jsp运行插件

(注意:使用springboot 2.6.7新建项目时已导入此插件,无需导入)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--
springboot插件
1.打包插件,可以以java -jar打包
在打成jar包运行时,必须放入此插件配置,没有插件配置无法运行打包的项目
2.可以正常显示jsp页面 可解决13.5页面无法显示问题
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

13.3开发jsp页面(==在main目录下新建webapp文件夹==,添加index.jsp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@page pageEncoding="utf-8" contentType="text/html; UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>springboot</title>
</head>
<body>
<h1>Hello, SpringBoot!</h1>
</body>
</html>

注意:如果新建的jsp页面为空白页面,可使用!+Tab进行模板补全

13.4 application.yml配置视图解析器

1
2
3
4
5
6
#在配置文件中引入视图解析器,配置jsp视图前缀和后缀  解析结果:前缀+controller返回直接+后缀
spring:
mvc:
view:
prefix: / # /代表访问项目中webapp中页面 #视图前缀
suffix: .jsp #视图后缀

13.5开发控制器

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

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

/**
* @ClassName JspController
* @Description 测试jsp模板集成
* @Author chabai
* @Date 2022/4/30 15:50
* @Version 1.0
*/
@Controller //RestController主要是针对前后端分离,我们传统开发不能用
@RequestMapping("jsp")
public class JspController {
//测试路径:http://localhost:8888/spring-boot-02/jsp/test
@RequestMapping("test")
public String test(){
System.out.println("jsp ok");
return "index";
}
}

启动服务进行测试,访问路径:http://localhost:8888/spring-boot-02/jsp/test,若找不到jsp页面,需要在idea中进行配置来启动jsp页面,下面有两种解决办法。

注意:使用springboot 2.6.7可以正常加载jsp页面,无需进行下面配置。

13.6idea中配置jsp页面

方式1:使用插件启动访问JSP页面 插件在13.2 不能再在main函数启动了 用插件启动

image-20240325000116562

方式2:在idea中指定工作目录启动访问JSP

image-20240325000205695

启动项目测试,访问路径:http://localhost:8888/spring-boot-02/index.jsp

13.7配置jsp页面修改后无需重启项目即可自动更新

application.yml

1
2
3
4
5
6
7
8
# 公共配置
server:
port: 8888 # 修改内置tomcat服务器的服务端口
servlet:
context-path: /spring-boot-02
jsp:
init-parameters:
development: true # 开启jsp页面开发模式,修改jsp页面无需重启springboot应用即可更新页面

14.整合Mybatis框架

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
springboot 微框架 = spring 工厂 + springmvc 控制器   微:快速开发框架  通过遵守默认约定  简化项目中样板化配置
数据库访问框架: hibernate jpa mybatis(框架)

回顾: spring框架如何整合mybatis?
1.引入依赖
spring 相关
mysql 相关 驱动 数据源
mybatis 相关 mybatis核心jar mybatis和spring框架整合

2.spring.xml
a.开启注解扫描
b.创建数据源对象
注入 指定使用那种数据源类型 注入dirverClassName 注入url 注入username password
c.创建sqlSessionFactory
注入 数据源 注入mapper配置文件位置 注入实体别名包
d.创建Dao对象
注入SqlSessionFactory 以及Dao接口所在包
e.创建事务管理器 DataSourceTranacationManager
注入 数据源对象
f.在业务层组件上加入事务注解 @Transacational
<tx:annotation-driven transaction-manager="transactionManager"/>

3.测试
1).建表
2).开发实体类
3).开发DAO接口
4).开发Mapper配置文件
5).开发Service接口
6).开发ServiceImpl实现类
7).测试ServiceImpl
===========================================================================
springboot框架中如何整合mybatis框架?
1.引入依赖
spring-boot-stater-web
mysql相关 mysql驱动 druid数据源
mybatis相关的 (mybatis-spring-boot-stater) 依赖 (mybatis mybatis-spring)

2.书写配置
a.开启注解扫描 @SpringBootApplication @ComponentScan 省略
b.创建数据源
1.指定数据源类型
2.指定数据库驱动
3.指定url
4.指定username
5.指定password
c.创建SqlSessionFactory
1.指定mapper配置文件位置
2.指定实体所在包位置 起别名
d.创建DAO
1.指定DAO接口所在包 并不在配置文件配置在入口类加注解即可
e.创建事务管理器 开启注解式事务生效 省略

3.测试
1).建表
2).开发实体类
3).开发DAO接口
4).开发Mapper映射文件
5).开发Service接口
6).开发ServiceImpl实现类
7).测试ServiceImpl

14.1 pom.xml中引入依赖

注意:springboot和java版本和mybatis和MySQL版本对应关系

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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>

<dependencies>
<!--spring-boot-starter-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!--实际只用引以下三个,使用idea快速创建springboot项目上边两个已经写好了-->
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>

<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>

<!--mybatis-spring-boot-starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>

14.2 application.yml编写相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 公共配置
server:
port: 8888 # 修改内置tomcat端口号
servlet:
context-path: /spring-boot-day03 #指定应用名称

# 整合mybatis数据源相关配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 指定连接池类型
driver-class-name: com.mysql.jdbc.Driver # mysql 5.x版本驱动
# driver-class-name: com.mysql.cj.jdbc.Driver # mysql 8.x版本驱动
url: jdbc:mysql://localhost:3306/bootssm?characterEncoding=UTF-8 # 指定url
username: root # 指定用户名和密码
password: 123456 # 指定密码

# mybatis相关配置
mybatis:
mapper-locations: classpath:com/study/mapper/*.xml # 指定mapper配置文件位置 当mapper接口和mapper接口对应的配置文件在 命名上相同 所在的路径相同可不配置
type-aliases-package: com.study.entity # 指定实体类的包名,默认别名:类名或类名首字母小写两种

14.3 启动类中添加注解扫描DAO接口所在包

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
package com.study;

import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

//入口类加入如下配置:
@SpringBootApplication
/**
* @Mapper 注解
* 修饰范围:只能放在DAO接口上,且每个DAO接口都需要放置此注解
* 作用:用来在工厂中创建DAO对象
*/
//@Mapper
/**
* @MapperScan 注解
* 修饰范围:用在类上
* 作用:用来扫描DAO接口所在包,同时将所有DAO接口在工厂中创建对象
*/
@MapperScan("com.study.dao")
public class SpringBootDay03Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootDay03Application.class, args);
}
}

————————————————-到这里springboot整合mybatis环境搭建就完成了,下边就是测试了———————————————

14.4 建表

1
2
3
4
5
6
7
8
9
CREATE DATABASE IF NOT EXISTS bootssm;
USER bootssm;
CREATE TABLE IF NOT EXISTS t_user(
id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
name VARCHAR(40) COMMENT '姓名',
birthday TIMESTAMP COMMENT '生日',
salary DOUBLE(10,2) COMMENT '工资',
PRIMARY KEY(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

14.5开发实体类

User

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
package com.study.entity;

import java.util.Date;

/**
* @ClassName User
* @Description TODO
* @Author chabai
* @Date 2022/5/3 10:02
* @Version 1.0
*/
public class User {
private Integer id;
private String name;
private Date birthday;
private Double salary;

public User() {
}

public User(Integer id, String name, Date birthday, Double salary) {
this.id = id;
this.name = name;
this.birthday = birthday;
this.salary = salary;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", birthday=" + birthday +
", salary=" + salary +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public Double getSalary() {
return salary;
}

public void setSalary(Double salary) {
this.salary = salary;
}
}

14.6 开发DAO接口以及Mapper映射文件

UserDAO mybatis只写接口实现是由mapper配置文件代替的

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.study.dao;

import com.study.entity.User;

import java.util.List;

/**
* @ClassName UserDAO
* @Description TODO
* @Author chabai
* @Date 2022/5/3 10:04
* @Version 1.0
*/
public interface UserDAO {
/**
* @MethodName findAll
* @Description 查询所有用户信息
* @return: java.util.List<com.study.entity.User>
* @Author chabai
* @Date 2022/5/3 10:04
*/
List<User> findAll();

/**
* @MethodName save
* @Description 保存用户信息
* @param: user
* @Author chabai
* @Date 2022/5/3 10:05
*/
void save(User user);
}

UserDAOMapper(resources:com/study/mapper) 位置要注意 到这里dao就写好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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.study.dao.UserDAO">
<!--findAll-->
<select id="findAll" resultType="User">
select
id,name,birthday,salary
from t_user
</select>

<!--save-->
<insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(#{id},#{name},#{birthday},#{salary})
</insert>

</mapper>

14.7 开发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
package com.study.service;

import com.study.entity.User;

import java.util.List;

/**
* @ClassName UserService
* @Description TODO
* @Author chabai
* @Date 2022/5/3 10:06
* @Version 1.0
*/
public interface UserService {
/**
* @MethodName findAll
* @Description 查询所有用户信息
* @return: java.util.List<com.study.entity.User>
* @Author chabai
* @Date 2022/5/3 10:04
*/
List<User> findAll();

/**
* @MethodName save
* @Description 保存用户信息
* @param: user
* @Author chabai
* @Date 2022/5/3 10:05
*/
void save(User user);
}

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.study.service;

import com.study.dao.UserDAO;
import com.study.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
* @ClassName UserServiceImpl
* @Description TODO
* @Author chabai
* @Date 2022/5/3 10:10
* @Version 1.0
*/
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{

/**@Autowired
* private UserDAO userDAO;
* 这种注入方法不再推荐了
*/

//推荐使用构造方式注入
private UserDAO userDAO;
@Autowired
public UserServiceImpl(UserDAO userDAO) {
this.userDAO = userDAO;
}

@Override
@Transactional(propagation = Propagation.SUPPORTS)
public List<User> findAll() {
return userDAO.findAll();
}

@Override
public void save(User user) {
userDAO.save(user);
}
}

14.8 开发Controller 测试一下整合是否成功

UserController

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
package com.study.controller;

import com.study.entity.User;
import com.study.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* @ClassName UserController
* @Description TODO
* @Author chabai
* @Date 2022/5/3 10:12
* @Version 1.0
*/
@RestController
@RequestMapping("user")
public class UserController {
private UserService userService; //这种测试方式在开发过程中不推荐,推荐本地测试

@Autowired
public UserController(UserService userService) {
this.userService = userService;
}

/**
* @MethodName findAll
* @Description 查询所有用户信息
* @return: java.util.List<com.study.entity.User>
* @Author chabai
* @Date 2022/5/3 10:14
*/
@RequestMapping("findAll")
public List<User> findAll(){
return userService.findAll();
}

/**
* @MethodName save
* @Description 添加用户信息
* @param: user
* @Author chabai
* @Date 2022/5/3 10:15
*/
@RequestMapping("save")
public void save(User user){
userService.save(user);
}
}

14.9 启动项目访问测试

查询所有用户访问地址:http://localhost:8888/spring-boot-day03/user/findAll

添加用户信息访问地址:http://localhost:8888/spring-boot-day03/user/save?name=赵六&birthday=2013/08/04&salary=2000

14.10 @MapperScan和@Mapper注解区别

1
2
3
4
5
6
7
8
9
10
11
/**
* @Mapper 注解
* 修饰范围:只能放在DAO接口上,每个接口都需要放置
* 作用:用来在工厂中创建DAO对象 只能创建单个Dao对象
*/

/**
* @MapperScan 注解 推荐使用
* 修饰范围:用在类上,可以一次性扫描所有DAO接口所在包中的接口
* 作用:用来扫描DAO接口所在包,同时将所有DAO接口在工厂中创建对象
*/

15.本地测试

本地测试:即再不启动springboot应用(SpringbootApplication)的情况下,如何测试service和dao

建议:在开发中推荐分层测试,先去测试dao,dao没问题的话再去测试业务逻辑,业务逻辑没问题的话再去启动应用通过controller测试

往往在开发过程中业务代码课程非常复杂频繁启动服务器测试,非常麻烦!这个时候使用本地测试就是一个很好的解决方案,springboot也提供了本地测试解决方案!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spring中本地测试
1.启动工厂
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
2.从工厂中获取指定对象
UserDao userDao = context.getBean("userDao");
3.调用方法
userDao.xxx(参数);


springboot框架中完成本地测试?
springboot = spring + springmvc
1.引入结合junit和springboot依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!--只能在测试时可用-->
<scope>test</scope>
</dependency>
2.启动springboot应用才能spring工厂启动,注入测试对象
@SpringBootTest
修饰范围: 用在类上
作用: 在这个类实例化过程中启动springboot应用

15.1 引入测试依赖

1
2
3
4
5
6
<!--spring-boot-starter-test集成了Junit单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

15.2 编写测试类(名字最好望文生义)

  • @SpringBootTest
    • 修饰范围:用在类上
    • 作用:用来启动本地Spring环境

项目自带的测试类XxxApplication(通过SpringInitializr创建的项目)

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

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootDay03ApplicationTests {

@Test
void contextLoads() {
}

}

实际应用时一般都是通过子测试类继承基本测试类(父测试类)来进行测试,具体如下:

父测试类:BasicTests 这样就不用每次都写@SpringBootTest注解了

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

import org.springframework.boot.test.context.SpringBootTest;

/**
* @ClassName BasicTests
* @Description SpringBoot基础测试类
* @Author chabai
* @Date 2022/5/5 10:27
* @Version 1.0
*/
@SpringBootTest
public class BasicTests {
}

子测试类:UserDAOTests

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
package com.study;

import com.study.dao.UserDAO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

/**
* @ClassName UserDAOTests
* @Description 测试UserDAO
* @Author chabai
* @Date 2022/5/5 10:29
* @Version 1.0
*/
public class UserDAOTests extends BasicTests{
@Autowired
private UserDAO userDAO; //注意:测试谁也是要先注入谁的

/**
* @MethodName testFindAll
* @Description 测试查询所有
* @Author chabai
* @Date 2022/5/5 10:31
*/
@Test
public void testFindAll(){
userDAO.findAll().forEach(user -> System.out.println("user = " + user));
}
}

子测试类:UserServiceTests

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
package com.study;

import com.study.entity.User;
import com.study.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

/**
* @ClassName UserServiceTests
* @Description 测试UserService
* @Author chabai
* @Date 2022/5/5 10:32
* @Version 1.0
*/
public class UserServiceTests extends BasicTests{
@Autowired
private UserService userService;

//测试查询所有
@Test
public void testFindAll(){
for (User user : userService.findAll()) {
System.out.println("user = " + user);
}
}
}

16.热部署工具

为了进一步提高开发效率,springboot为我们提供了全局项目热部署,日后在开发过程中如果修改了部分代码以及相关配置文件后,不需要每次重启项目使修改生效,在项目中如果开启了springboot全局热部署之后,只需要在修改之后等待几秒即可使修改生效【底层通过强制刷新生成新的.class文件,jvm通过对比旧class与新class进行替换,运行新的class文件,并没有重启整个springboot】。
16.1 pom.xml中引入依赖 (每次搭建项目后都需要引入)

1
2
3
4
5
6
7
8
9
<!--热部署依赖 devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!--optional:用于表示该依赖是否可以进行传递 A->B 若B有devtools并开启依赖传递,则A也有devtools
false:能传递(默认)
true:不能传递,只在当前项目可用-->
<optional>true</optional>
</dependency>

添加依赖后记得刷新Maven,否则热部署不能生效!!!

16.2 设置idea中支持自动编译(只需要设置一次)

1.开启自动编译(只要不换电脑或者重新安装idea,此操作只进行一次即可)

实现:

(1)视频中操作:Preferences | Build, Execution, Deployment | Compiler -> 勾选上 Build project automatically 这个选项

(2)实际操作:File—Setting—Build,Execution,Deployment—Compiler—勾选上Build project automatically选项—Apply—OK
SS

image-20240325002851407

2.开启允许在运行过程中修改文件

实现:

低版本

任意空白位置—Ctrl+Alt+Shift+/—选择1.Registry…—勾选compiler.automake.allow.when.app.running选项

image-20240325002933210

image-20240325002946244

高版本

image-20240403231330351

16.3 启动项目检测热部署是否生效

修改内容后进行保存,等待项目自动进行编译,日志中由main变为restartedMain表示热部署生效

image-20240325003019099

1
2
3
4
5
6
7
8
9
10
11
12
13
2022-05-16 16:15:49.676  INFO 6128 --- [  restartedMain] com.study.SpringBootDay03Application     : Starting SpringBootDay03Application using Java 1.8.0_131 on cjn-PC with PID 6128 (D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day03\target\classes started by cjn in D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day03)
2022-05-16 16:15:49.676 INFO 6128 --- [ restartedMain] com.study.SpringBootDay03Application : No active profile set, falling back to default profiles: default
2022-05-16 16:15:49.825 INFO 6128 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8888 (http)
2022-05-16 16:15:49.826 INFO 6128 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-05-16 16:15:49.826 INFO 6128 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.46]
2022-05-16 16:15:49.843 INFO 6128 --- [ restartedMain] o.a.c.c.C.[.[.[/spring-boot-day03] : Initializing Spring embedded WebApplicationContext
2022-05-16 16:15:49.843 INFO 6128 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 165 ms
2022-05-16 16:15:49.949 INFO 6128 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2022-05-16 16:15:49.959 INFO 6128 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8888 (http) with context path '/spring-boot-day03'
2022-05-16 16:15:49.962 INFO 6128 --- [ restartedMain] com.study.SpringBootDay03Application : Started SpringBootDay03Application in 0.306 seconds (JVM running for 259.773)
2022-05-16 16:15:49.963 INFO 6128 --- [ restartedMain] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to CORRECT
2022-05-16 16:15:49.964 INFO 6128 --- [ restartedMain] .ConditionEvaluationDeltaLoggingListener : Condition evaluation unchanged
2022-05-16 16:15:49.964 INFO 6128 --- [ restartedMain] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC

注意:日志出现restartedMain代表已经生效,在使用热部署时如果遇到修改之后不能生效,请重试重启项目再试

17.日志处理

17.1 引言

springboot框架集成了logback 日志, Logback是由log4j创始人设计的又一个开源日志组件。目前,logback分为三个模块:logback-core,logback-classic和logback-access,logback-core是其它两个模块的基础模块;logback-classic是log4j的一个改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging;logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。该日志是对log4j日志展示进一步改进!即: logback 也是一个开源日志组件,和 log4j作用一致,都是用来生成日志,两者相比logback更加轻量!

17.2日志的级别

级别从下到上依次提高,级别越高,输出的信息越详细:

image-20240325003610332

级别从高到低依次为: All < Trace < DEBUG < INFO < WARN < ERROR < Fatal < OFF

  • OFF | 关闭:最高级别,不打印日志。
  • FATAL | 致命:指明非常严重的可能会导致应用终止执行错误事件。
  • ERROR | 错误:指明错误事件,但应用可能还能继续运行。
  • WARN | 警告:指明可能潜在的危险状况。
  • INFO | 信息:指明描述信息,从粗粒度上描述了应用运行过程。
  • DEBUG | 调试:指明细致的事件信息,对调试应用最有用。
  • TRACE | 跟踪:指明程序运行轨迹,比DEBUG级别的粒度更细。
  • ALL | 所有:所有日志级别,包括定制级别。

日志级别由低到高: 日志级别越高输出的日志信息越多

17.3 项目中日志分类

  • 一种是rootLogger(根全局日志) : 用来监听项目中所有的运行日志,包括引入依赖jar中的日志
  • 一种是logger(指定包级别日志) : 用来监听项目中指定包中的日志信息

17.4配置日志

注意:SpringBoot框架中默认根日志为INFO级别

logging:
level:
root: debug # 指定根日志级别(一般不推荐修改根日志,输出信息太多,推荐使用子日志)
com.study.dao: debug #指定dao包中日输出级别
file:
name: debug.log # 指定生成的日志另存为文件的名称
path: ./ # 指定日志文件在项目目录下保存

17.5 项目中使用日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
public class HelloController {
//声明日志成员
private static final Logger log = LoggerFactory.getLogger(HelloController.class);
@RequestMapping("/hello")
@ResponseBody
public String hello(){
System.out.println("======hello world=======");
log.debug("DEBUG,{}","信息");
log.info("INFO,{}","信息"); //logi才能正常导入类
log.warn("WARN,{}","信息");
log.error("ERROR,{}","信息");
return "hello";
}
}

17.6具体实现

新建项目spring-boot-day4,新建包、类

a.导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>

<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>

<!--mybatis-spring-boot-stater -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>

b.修改application.properties为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
# 公共配置
server:
port: 8888
servlet:
context-path: /spring-boot-day4

# 整合Mybatis
# 数据库配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/bootssm?characterEncoding=UTF-8&useSSL=true
username: root
password: 123456

# 别名配置
mybatis:
mapper-locations: classpath:com/study/mapper/*.xml
type-aliases-package: com.study.entity

# 配置日志
logging:
level:
root: info # 默认根日志级别为info
com.study.dao: debug # 指定某个包的输出日志级别
com.study.service: debug
file:
name: run.log # 指定生成日志文件的名称
path: ./ # 将日志文件保存在当前项目目录下

c.新建实体类

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
package com.study.entity;

import javafx.beans.binding.DoubleExpression;

import java.util.Date;

/**
* @ClassName User
* @Description TODO
* @Author chabai
* @Date 2022/5/28 11:24
* @Version 1.0
*/
public class User {
private Integer id;
private String name;
private Date birthday;
private Double salary;

public User() {
}

public User(Integer id, String name, Date birthday, Double salary) {
this.id = id;
this.name = name;
this.birthday = birthday;
this.salary = salary;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", birthday=" + birthday +
", salary=" + salary +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public Double getSalary() {
return salary;
}

public void setSalary(Double salary) {
this.salary = salary;
}
}

d.新建DAO接口

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
package com.study.dao;

import com.study.entity.User;

import java.util.List;

/**
* @ClassName UserDAO
* @Description TODO
* @Author chabai
* @Date 2022/5/28 11:27
* @Version 1.0
*/
public interface UserDAO {
/**
* @MethodName findAll
* @Description 查询所有
* @return: java.util.List<com.study.entity.User>
* @Author chabai
* @Date 2022/5/28 11:28
*/
List<User> findAll();

/**
* @MethodName save
* @Description 新增用户
* @param: user
* @Author chabai
* @Date 2022/5/28 11:28
*/
void save(User user);
}

e.新建Mapper文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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.study.dao.UserDAO">
<!--查询所有-->
<select id="findAll" resultType="User">
select id,name,birthday,salary from t_user
</select>

<!--新增用户-->
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into t_user values (#{id},#{name},#{birthday},#{salary})
</insert>
</mapper>

f.新建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
package com.study.service;

import com.study.entity.User;

import java.util.List;

/**
* @ClassName UserService
* @Description TODO
* @Author chabai
* @Date 2022/5/28 11:34
* @Version 1.0
*/
public interface UserService {
/**
* @MethodName findAll
* @Description 查询所有
* @return: java.util.List<com.study.entity.User>
* @Author chabai
* @Date 2022/5/28 11:28
*/
List<User> findAll();

/**
* @MethodName save
* @Description 新增用户
* @param: user
* @Author chabai
* @Date 2022/5/28 11:28
*/
void save(User user);
}

h.新建ServiceImpl

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
package com.study.service;

import com.study.dao.UserDAO;
import com.study.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
* @ClassName UserServiceImpl
* @Description TODO
* @Author chabai
* @Date 2022/5/28 11:34
* @Version 1.0
*/
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
@Autowired
public UserServiceImpl(UserDAO userDAO) {
this.userDAO = userDAO;
}

@Override
@Transactional(propagation = Propagation.SUPPORTS)
public List<User> findAll() {
return userDAO.findAll();
}

@Override
public void save(User user) {
userDAO.save(user);
}
}

i.新建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
package com.study.controller;

import com.study.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
* @ClassName UserController
* @Description TODO
* @Author chabai
* @Date 2022/5/28 12:03
* @Version 1.0
*/
@RestController
@RequestMapping("user")
public class UserController {
//声明日志成员
private static final Logger log = LoggerFactory.getLogger(UserController.class);

private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}

@RequestMapping("test")
public String test(){
log.debug("debug,{}","信息1");//根日志默认为info级别,不能输出debug信息
log.info("info,{}","信息2");
log.warn("warn,{}","信息3");
log.error("error,{}","信息4");
return "Ok";
}
}

17.7IDEA安装Log Support 2插件

方法1:在Settings的Plungins中搜索插件安装,安装后重启IDEA

image-20240325004318708

方法2:搜索不到时,下载插件进行离线安装(idea 2020版本好像不兼容Log Support 2 2021版本)

image-20240325004335683

idea官网下载离线安装:JetBrains Marketplace

image-20240325004359483

下载压缩包后不用解压,通过Install Plugin from Disk选择下载好的压缩包即可,之后重启idea。

image-20240325004417618

安装好插件后,重启idea,还需进行如下配置:

(1)选择slf4j

image-20240325004439337

(2)取消AndroidLog

image-20240325004454986

之后就可以使用了,比如键入logi就会出现log.info(“”);并且自动添加private static final Logger log = LoggerFactory.getLogger(UserController.class);其中,logd 代表log.debug、logw 代表log.warn…以此类推。

注意:以后切换工作空间或者打开一个新的项目,都需要在设置中的Log Support中制定一下需要使用的日志框架

18.面向切面编程(AOP)

18.1引言

springboot是对原有项目中spring框架和springmvc的进一步封装,因此在springboot中同样支持spring框架中AOP切面编程,不过在springboot中为了快速开发仅仅提供了注解方式的切面编程。

18.2 项目环境搭建

新建项目spring-boot-day5

1.修改application.properties为application.yml,添加配置信息

1
2
3
4
server:
port: 8888
servlet:
context-path: /spring-boot-day5

2.引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3.编写Service及其实现类Impl

UserService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.study.service;

/**
* @ClassName UserService
* @Description TODO
* @Author chabai
* @Date 2022/5/31 17:51
* @Version 1.0
*/
public interface UserService {
void insert(String name);
void delete(Integer id);
void update(String name);
String select(String name);
}

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
28
29
30
31
32
33
34
35
36
package com.study.service;

import com.study.annotations.MyAdvice;
import org.springframework.stereotype.Service;

/**
* @ClassName UserServiceImpl
* @Description TODO
* @Author chabai
* @Date 2022/5/31 17:54
* @Version 1.0
*/
@Service
public class UserServiceImpl implements UserService{
@Override
public void insert(String name) {
System.out.println("处理insert核心业务逻辑,调用DAO~~");
}

@Override
public void delete(Integer id) {
System.out.println("处理delete核心业务逻辑,调用DAO~~");
}

@Override
public void update(String name) {
System.out.println("处理update核心业务逻辑,调用DAO~~");
}

@Override
@MyAdvice //在使用基于注解的切入点表达式时自定义的注解
public String select(String name) {
System.out.println("处理select核心业务逻辑,调用DAO~~");
return name;
}
}

18.3切面使用

1
2
3
4
5
6
7
8
9
10
# 切面注解
- @Aspect 用来类上,代表这个类是一个切面 Aspect 切面 = Advice 附加操作 + Pointcut 切入点
- @Before 用在方法上代表这个方法是一个前置通知方法
- @After 用在方法上代表这个方法是一个后置通知方法
- @Around 用在方法上代表这个方法是一个环绕的方法

# 切入点表达式:
1.execution 方法级别切入点表达式 save update 运行效率越低
2.within 类级别切入点表达式: 控制越粗 运行效率越高
3.基于注解的切入点表达式 @annotation(com.baizhi.annotations.Xxx),需要自定义通知Xxx,且需要YyyServiceImpl业务逻辑方法上添加@Xxx注解,均配置后才可使用

切面配置类

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
package com.study.config;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Configuration;

/**
* @ClassName MyAspectConfig
* @Description TODO
* @Author chabai
* @Date 2022/5/31 18:40
* @Version 1.0
*/
@Configuration //代表当前这个类是一个spring的配置类,相当于spring.xml
@Aspect //代表这是一个切面配置类
public class MyAspectConfig {
/**
* @MethodName before
* @Description 前置通知
* @param: joinPoint
* @Author chabai
* @Date 2022/6/2 9:22
*/
// @Before("execution(* com.study.service.*.*(..))")
// public void before(JoinPoint joinPoint){
// System.out.println("前置附加操作");
// System.out.println("当前执行目标类:" + joinPoint.getTarget());
// System.out.println("当前执行目标类中方法:" + joinPoint.getSignature().getName());
// System.out.println("当前执行目标类中方法参数:" + joinPoint.getArgs());
// }

/**
* @MethodName after
* @Description 后置通知
* @param: joinPoint
* @Author chabai
* @Date 2022/6/2 9:22
*/
// @After("within(com.study.service.*))")
// public void after(JoinPoint joinPoint){
// System.out.println("后置附加操作");
// System.out.println("当前执行目标类:" + joinPoint.getTarget());
// System.out.println("当前执行目标类中方法:" + joinPoint.getSignature().getName());
// System.out.println("当前执行目标类中方法参数:" + joinPoint.getArgs());
// }


/**
* @MethodName around
* @Description 环绕通知
* @param: proceedingJoinPoint
* @return: java.lang.Object 用来将业务方法返回结果给调用者
* @Author chabai
* @Date 2022/6/2 9:21
*/
//@Around("execution(* com.study.service.*.*(..))")
@Around("@annotation(com.study.annotations.MyAdvice)") //仅对业务逻辑方法上含有@MyAdvice注解的方法生效
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前置附加操作");
System.out.println("当前执行目标类:" + proceedingJoinPoint.getTarget());
System.out.println("当前执行目标类中方法:" + proceedingJoinPoint.getSignature().getName());
System.out.println("当前执行目标类中方法参数:" + proceedingJoinPoint.getArgs());
//进行目标方法的放行操作,即:继续执行业务逻辑方法
Object proceed = proceedingJoinPoint.proceed();
System.out.println("proceed = " + proceed);
System.out.println("环绕后置附加操作");
return proceed;
}
}

自定义通知注解

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @ClassName MyAdvice
* @Description TODO
* @Author chabai
* @Date 2022/6/2 9:33
* @Version 1.0
*/
//指定运行时生效
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAdvice {
}

注意:

1.前置通知和后置通知都没有返回值,方法参数都为JointPoint

2.环绕通知有返回值,方法参数为ProceedingJoinPoint,需要抛出异常。一旦执行放行必须将目标方法的返回值返回,否则调用者无法接收返回数据

18.4 测试

BasciTests

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

import org.springframework.boot.test.context.SpringBootTest;

/**
* @ClassName BasicTests
* @Description TODO
* @Author chabai
* @Date 2022/5/31 19:06
* @Version 1.0
*/
@SpringBootTest
public class BasicTests{
}

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
28
29
30
31
32
33
34
35
36
37
38
39
package com.study;

import com.study.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

/**
* @ClassName UserServiceTest
* @Description TODO
* @Author chabai
* @Date 2022/5/31 19:08
* @Version 1.0
*/
public class UserServiceTest extends BasicTests{

@Autowired
private UserService userService;

@Test
public void testInsert(){
userService.insert("茶白");
}

@Test
public void testDelete(){
userService.delete(1);
}

@Test
public void testUpdate(){
userService.update("加油");
}

@Test
public void testSelect(){
String name = userService.select("越努力,越幸运");
System.out.println(name);
}
}

18.5 总结

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
94
95
96
97
98
99
100
101
102
103
104
105
===================保存用户业务逻辑=================
用户业务
UserService

void save(User user)

void delete(Integer id);

User queryById(Integer id);

....


UserServiceImpl implement UserService

void save(User user){
sout("=============");//业务功能-1
sout("*************");//业务功能-2
...
userDao.save(user);
}

void delete(Integer id){
sout("=============");//业务功能-1
sout("*************");//业务功能-2
......
userDao.delete(id);
}

User queryById(Integer id){
sout("=============");//业务功能-1
sout("*************");//业务功能-2
.....
return userDao.queryById(id)
}


=================保存用户业务逻辑==================
加入新的功能:
保存用户之前: 打印输出一句话 "==========="

================用户业务逻辑======================

加入新的功能:
保存|删除|修改|查询用户之前: 打印输出一句话 "==========="
保存|删除|修改|查询用户之前: 打印输出一句话 "***********"
================================================
问题:
1.现有业务层开发存在问题
a.-->额外功能代码存在大量冗余?
b.-->每个方法都需要书写一遍额外功能代码不利于后续项目维护?

Spring 框架
AOP: Aspect(切面) Oriented(面向) Programmaing 面向切面编程

Aspect (切面) = Advice(通知) + 切入点(Pointcut)

Advice 通知: 业务逻辑中一些附加操作称之为通知 前置 后置 环绕
Pointcut 切入点: 配置通知应用于项目中那些业务操作

Aspect 切面 = 附加操作(Advice) + 切入点(Pointcut)(配置)

1.类 implement xxAdvice接口

2.XML进行配置
<aop:config>
<aop:pointcut id="pc" expresssion="execution(* 包.类.方法名(方法参数))|within(类级别)|@annotation(注解类型)">
<aop:advisor advice-ref="通知类" pointcut-ref="pc"/>
</aop:config>



SpringBoot框架 现有spring框架 进一步封装
1.无xml配置 一切皆java配置
AOP: 面向切面编程
Aspect 切面 = Advice(通知) + 切入点

1.开发附加操作 Advice
2.配置切入点&组装切面


面向切面编程步骤:
1.引入aop切面编程依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.在springboot项目中新建config(配置)包
@Configuration //修饰范围: 只能用在类上 作用: 代表这是一个spring的配置类 spring.xml
@Aspect //修饰范围: 只能用在类上 作用:代表这个类是一个切面类 <aop:config>
MyAspectConfig(自定义切面类) 配置类{

//@Before: 代表这个方法是一个前置附加操作
//@After : 代表这个方法是一个后置附加操作
注意: 使用@Before 和 @After注解声明方法上加入一个参数 定义一个参数 JointPoint 连接点
//@Around: 代表这个方法是一个环绕附加操作
value属性: 用来书写切入点表达式
注意: 使用@Around注解 在方法定义时声明一个参数: ProceedingJoinPoint 处理过程中连接点

@Before("execution(* com.baizhi.service.*.*(..))")
public void before(){
sout("===========")
}
}

19.文件上传

文件上传:用户访问当前系统,将自己本地计算机中文件通过浏览器上传到当前系统所在的服务器或文件服务器(OSS阿里云对象存储、mino对象存储、七牛云文件存储)的过程

19.1 思路

1
2
3
4
a.提供一张上传页面  (此处以jsp页面为例) 
提交方式必须:post
enctype属性必须为 multipart/form-data(文本类型、二进制类型文件均可做编码),默认的application/x-www-form-urlencoded只能对文本类型(字符串)进行编码
b.开发上传controller

19.2 准备上传页面

upload.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@page pageEncoding="UTF-8" contentType="text/html; UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>文件上传</title>
</head>
<body>
<h1>测试文件上传</h1>
<form action="${pageContext.request.contextPath}/file/uploadByJarDeploy" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传文件">
</form>
</body>
</html>

success.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<%@page pageEncoding="UTF-8" contentType="text/html; UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>文件上传成功</title>
</head>
<body>
<h1>恭喜您,文件上传成功!</h1>
</body>
</html>

19.3导入依赖

1
2
3
4
5
<!--解析jsp模板依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>

19.4编写配置文件

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
server:
port: 8888
servlet:
context-path: /spring-boot-day5
jsp: # 打开jsp开发模式
init-parameters:
development: true

# springmvc相关配置
spring:
mvc:
view:
prefix: / # 配置视图前缀
suffix: .jsp # 配置视图后缀
servlet:
multipart: # 修改文件上传的大小限制,默认10M(10485760B)
max-request-size: 120MB # 运行请求传递文件大小最大为120MB,单位可以直接写MB
max-file-size: 120MB # 运行服务器可以处理的最大文件大小为120MB
profiles:
active: local # 激活本地配置生效 激活哪个包括哪个

# 调整日志
logging:
level:
root: info # 默认info级别日志
com.study: debug # 开启com.study包中所有debug日志

application-local.yml 第二种上传方式配置 主配置文件一定要激活这个配置文件

1
2
3
4
# 指定文件上传位置
file:
upload:
dir: D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day5\src\main\webapp\upload\local # 指定本地上传测试目录

application-prod.yml

1
2
3
4
# 指定文件上传位置
file:
upload:
dir: D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day5\src\main\webapp\upload\prod # 指定生产上传测试目录

19.5编写控制器

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
94
95
96
97
98
package com.study.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.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
* @ClassName FileController
* @Description TODO
* @Author chabai
* @Date 2022/6/2 17:40
* @Version 1.0
*/
@Controller
@RequestMapping("file")
public class FileController {
//创建日志对象
private static final Logger log = LoggerFactory.getLogger(FileController.class);

/**
* @MethodName upload
* @Description 用来测试文件上传,注意此种方式不能用于jar包部署
* 由于这种方式存在局限性,所以现在不推荐使用了
* @param: file 定义接收文件对象multipartFile,file变量名要与form中input type="file"标签name属性名一致
* @param: request
* @return: java.lang.String
* @Author chabai
* @Date 2022/6/2 18:02
*/
@RequestMapping("upload")
public String upload(MultipartFile file, HttpServletRequest request) throws IOException {
//获得原始文件名
String originalFilename = file.getOriginalFilename();
log.debug("文件名:{}", originalFilename);
log.debug("文件大小:{}",file.getSize());//以字节为单位
log.debug("文件类型:{}",file.getContentType());

//1.根据相对上传路径"/upload"获取绝对路径(真实路径) 动态获取
// 即:/users/桌面... 或 /home/springboot_day5/...
String realPath = request.getSession().getServletContext().getRealPath("/upload");
log.debug("获取绝对路径:{}",realPath);

//2.上传文件
//获取文件名后缀 FileNameUtil工具类需要引入jar包
String ext = originalFilename.substring(originalFilename.lastIndexOf("."));
//拼接新文件名 时间戳 SSS毫秒
String newFileName = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + ext;
System.out.println("新文件名:" + newFileName);
//上传文件 参数1: 将文件写入到那个目录 参数2:文件名
file.transferTo(new File(realPath, newFileName));
return "redirect:/success.jsp";
}

//早期war文件部署到服务器tomcat中,可以解压得到目录
//现在springboot内嵌tomcat,jar包不会解压,找不到目录

//获得文件路径
@Value("${file.upload.dir}")
private String realPath;

/**
* @MethodName uploadByJarDeploy
* @Description 用来测试文件上传,这种方式适用于任何一种部署方式,推荐使用这种方式
* @param: file
* @return: java.lang.String
* @Author chabai
* @Date 2022/6/3 19:05
*/
@RequestMapping("uploadByJarDeploy") //测试路径:http://localhost:8888/spring-boot-day5/upload.jsp
public String uploadByJarDeploy(MultipartFile file) throws IOException {
//获得文件原始名
String originalFilename = file.getOriginalFilename();
log.debug("文件名:{}",originalFilename);
log.debug("文件大小:{}",file.getSize());
log.debug("文件类型:{}",file.getContentType());

//获取文件后缀
String ext = originalFilename.substring(originalFilename.lastIndexOf("."));
//改名
String newFileName = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + ext;

//上传文件
//file.transferTo(new File(d:/,newFileName)); //这样路径写死了,我们可以采用配置文件注入方式
file.transferTo(new File(realPath,newFileName));

return "redirect:/success.jsp";
}
}

注意:

1
2
3
4
5
6
7
8
9
10
11
#上传时出现如下异常:  上传文件的大小超出默认配置      默认10M
nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (38443713) exceeds the configured maximum (10485760)
#修改上传文件大小:
spring:
http:
multipart:
max-request-size: 209715200 #用来控制文件上传大小的限制
max-file-size: 209715200 #用来指定服务端最大文件大小,可以直接使用MB

spring.servlet.multipart.max-file-size=500MB
spring.servlet.multipart.max-request-size=500MB

19.6 项目目录结构

image-20240325010223071

20.文件下载

文件下载:将服务器某个资源文件下载到用户本地计算机过程

20.1思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a.确定项目中哪些资源可以被下载  aa.txt 用户须知.doc  .....

b.将可以被下载资源放入自己服务器指定位置(以这个演示)、文件上传服务器fastdfs(dfs 分布式文件存储系统 1000个节点 冗余备份)、上传到OSS对象存储七牛云(方便但是贵啊)......
/home/download 自己服务器磁盘有限
aa.txt
用户须知.doc
.....
/Users/chenyn/Desktop/线上课/项目阶段/01springboot/codes/springboot_day6/download
目录下放可被下载文件

c.项目中开发一个下载页面download.jsp
提供下载文件链接

d.开发下载控制器controller

20.2新建项目,导入依赖

1
2
3
4
5
6
7
<!--tomcat解析jsp-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<!--provided表示当前idea环境可用,打包时不参与打包-->
<scope>provided</scope>
</dependency>

20.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
server:
port: 8888
servlet:
context-path: /spring-boot-day6
jsp:
init-parameters:
development: true

spring:
mvc:
view:
prefix: /
suffix: .jsp

file:
download:
dir: D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day6\download # 指定下载目录测试环境 生产环境记得改
#dir: /home/download


logging:
level:
root: info
com.study: debug

20.4开发jsp页面 新建webapp目录新建jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@page pageEncoding="UTF-8" contentType="text/html; UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>测试文件下载</title>
</head>
<body>
<h1>文件下载页面</h1>
<a href="${pageContext.request.contextPath}/file/download?fileName=HELP.md">HELP.md</a>
<a href="${pageContext.request.contextPath}/file/download?fileName=readme.txt">readme.txt</a>
<a href="${pageContext.request.contextPath}/file/download?fileName=说明.doc">说明.doc</a>
<a href="${pageContext.request.contextPath}/file/download?fileName=图片.jpg">图片.jpg</a>
</body>
</html>

20.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
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
package com.study.controller;

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 javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;

/**
* @ClassName FileController
* @Description TODO
* @Author chabai
* @Date 2022/6/3 22:59
* @Version 1.0
*/
@Controller
@RequestMapping("file")
public class FileController {
//声明日志对象
private static final Logger logger = LoggerFactory.getLogger(FileController.class);

//绝对路径
@Value("${file.download.dir}")
private String realPath;

/**
* @MethodName download
* @Description 测试文件下载
* @param: fileName
* @param: response
* @Author chabai
* @Date 2022/6/3 23:14
*/
@RequestMapping("download") //测试路径:http://localhost:8888/spring-boot-day6/download.jsp
public void download(String fileName, HttpServletResponse response) throws IOException {
logger.debug("当前下载文件名为:{}",fileName);
logger.debug("当前下载文件路径为为:{}",realPath);

//根据文件名去服务器指定位置读取文件
//1.指定在绝对路径中读取文件
File file = new File(realPath, fileName);

//2.将文件读取为文件输入流 拿到文件输入流内容我们把文件输入流内容写道响应输出流就可以了
FileInputStream fis = new FileInputStream(file);

//3.获取响应流之前一定要设置以附件形式下载,否则只能在浏览器进行页面展示;为防止文件名中文乱码,需要指定编码
//key:"content-disposition" ,value:"attachment;" 附件形式
response.setHeader("content-disposition","attachment;filename="+ URLEncoder.encode(fileName,"UTF-8"));

//4.获取响应输出流
ServletOutputStream sos = response.getOutputStream();

//5.将输入流复制给输出流 老方法
// int len = 0;
// byte[] data = new byte[1024];
// while (true){
// len = fis.read(data);
// if(len == -1)
// break;
// sos.write(data,0,len);
// }
// fis.close();

//5.spring工具类
//将输入流复制给输出流
FileCopyUtils.copy(fis,sos);
}
}

20.6项目目录结构

image-20240325010419202

21.拦截器

21.1拦截器介绍 interceptor

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
filter 过滤器: 过滤器可以拦截javaweb中请求,进行放行、中断等功能  
强大之处: 可以拦截一切资源 .jsp、.html、.css、.img .....

1.定义: 拦截器类似于javaweb中filter功能,但只能拦截controller相关的请求

2.作用: 将controller中共有代码放入到拦截器中执行,减少controller中代码冗余

3.拦截器特性:
1).拦截器只能拦截controller相关请求
2).拦截器可以中断请求轨迹
3).请求之前如果该请求配置了拦截器,请求会先经过拦截器,拦截器放行之后执行请求的controller,controller执行完成之后会回到拦截器继续执行拦截器中的代码

4.拦截器开发
a.类 implement HandlerInterceptor接口 实现接口的类有默认实现的preHandler、postHandler、afterCompetition方法 可选配置
preHandler 预先处理方法: 最先执行方法,返回值布尔类型 true:放行请求、fals:中断(后续不再执行)
返回结果为true时执行controller中内容
postHandler 过程中处理: controller返回之后回到postHandler这个方法执行,执行完成这个方法开始响应浏览器
afterCompletion 最后完成: 当响应结束之后会执行拦截器中这个方法内容
b.配置拦截器
springmvc配置方式:mvc:interceptors springmvc.xml
springboot 提供了springmvc配置类: WebMvcConfigurer
类 implement WebMvcConfigurer接口{
//覆盖配置拦截器方法
1.使用哪个拦截器 2.拦截器拦截请求 3.排除那些请求
}

21.2开发拦截器 新建目录interceptors放我们的拦截器

MyInterceptor1

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
package com.study.interceptors;

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;

/**
* @ClassName MyInterceptor1
* @Description 自定义拦截器1
* @Author chabai
* @Date 2022/6/4 12:20
* @Version 1.0
*/
public class MyInterceptor1 implements HandlerInterceptor {
//日志对象
private static final Logger logger = LoggerFactory.getLogger(MyInterceptor1.class);

/**
* @MethodName preHandle
* @Description 最先执行的处理方法
* @param: request 请求对象
* @param: response 响应对象
* @param: handler 当前请求请求的控制器方法对象 XXXController#yyyy
* @return: boolean
* @Author chabai
* @Date 2022/6/4 12:23
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// logger.debug("handler:{}",handler);
logger.debug("1");
// response.sendRedirect(request.getContextPath()+"/403.jsp"); //一旦中断进行跳转
return true;//放行
}

/**
* @MethodName postHandle
* @Description 执行控制器方法后执行该方法
* @param: request 请求对象
* @param: response 响应对象
* @param: handler 当前请求请求的控制器方法对象 XXXController#yyyy
* @param: modelAndView 视图和模型,当前请求访问方法的ModelAndView对象
* @Author chabai
* @Date 2022/6/4 12:26
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// logger.debug("modelAndView:{}",modelAndView);//view="xxx"; model={xxx}
// logger.debug("model:{}",modelAndView.getModel());
// logger.debug("view:{}",modelAndView.getViewName());
logger.debug("2");
}

/**
* @MethodName afterCompletion
* @Description 最后执行的方法,该方法类似于finally{}代码块,无论请求是否正确或出现异常总会执行
* @param: request 请求对象
* @param: response 响应对象
* @param: handler 当前请求请求的控制器方法对象
* @param: ex 控制器出现异常时的异常对象
* @Author chabai
* @Date 2022/6/4 12:31
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.debug("3");
}
}

MyInterceptor2

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
package com.study.interceptors;

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;

/**
* @ClassName MyInterceptor2
* @Description 自定义拦截器2
* @Author chabai
* @Date 2022/6/4 12:20
* @Version 1.0
*/
public class MyInterceptor2 implements HandlerInterceptor {
//日志对象
private static final Logger logger = LoggerFactory.getLogger(MyInterceptor2.class);

/**
* @MethodName preHandle
* @Description 最先执行的处理方法
* @param: request 请求对象
* @param: response 响应对象
* @param: handler 当前请求请求的控制器方法对象 XXXController#yyyy
* @return: boolean
* @Author chabai
* @Date 2022/6/4 12:23
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// logger.debug("handler:{}",handler);
logger.debug("4");
return true;//放行
}

/**
* @MethodName postHandle
* @Description 执行控制器方法后执行该方法
* @param: request 请求对象
* @param: response 响应对象
* @param: handler 当前请求请求的控制器方法对象 XXXController#yyyy
* @param: modelAndView 视图和模型,当前请求访问方法的ModelAndView对象
* @Author chabai
* @Date 2022/6/4 12:26
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// logger.debug("modelAndView:{}",modelAndView);//view="xxx"; model={xxx}
// logger.debug("model:{}",modelAndView.getModel());
// logger.debug("view:{}",modelAndView.getViewName());
logger.debug("5");
}

/**
* @MethodName afterCompletion
* @Description 最后执行的方法,该方法类似于finally{}代码块,无论请求是否正确或出现异常总会执行
* @param: request 请求对象
* @param: response 响应对象
* @param: handler 当前请求请求的控制器方法对象
* @param: ex 控制器出现异常时的异常对象
* @Author chabai
* @Date 2022/6/4 12:31
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.debug("6");
}
}

21.3配置拦截器 新建config放我们的配置类

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
package com.study.config;

import com.study.interceptors.MyInterceptor1;
import com.study.interceptors.MyInterceptor2;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @ClassName MvcConfig
* @Description 配置拦截器
* @Author chabai
* @Date 2022/6/4 12:34
* @Version 1.0
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**
* @MethodName addInterceptors
* @Description 添加拦截器
* @param: registry 注册拦截器
* @Author chabai
* @Date 2022/6/4 12:35
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor1()) //指定拦截器
.addPathPatterns("/**") //添加拦截路径,此处拦截所有
.excludePathPatterns("/file/**") //排除/不拦截的路径
.order(1);//指定拦截器执行顺序,参数为int类型数字,默认按照自然排序执行,数字相同时,按照配置顺序先后执行
registry.addInterceptor(new MyInterceptor2())
.addPathPatterns("/**")
.excludePathPatterns("/file/**")
.order(2); //145263 order(1) 和 order(2)互换 412536
}
}

注意:order用来指定多个拦截器的执行顺序,order书写是自然数,按照自然数从小到大顺序执行

21.4开发控制器

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
package com.study.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @ClassName InterceptorController
* @Description TODO
* @Author chabai
* @Date 2022/6/4 12:33
* @Version 1.0
*/
@Controller
@RequestMapping("interceptor")
public class InterceptorController {
private static final Logger logger = LoggerFactory.getLogger(InterceptorController.class);

@RequestMapping("test")
public String test(){
logger.debug("test is ok!");
return "success";//此处返回ModelAndView对象,会自动封装,此时只有view:success.jsp
//传统的web开发虽然这里写了一个字符串,但是底层也是把字符串包装成ModelAndView对象返回 我们这里只有view 会把succes字符串渲染成一个forward /success.jsp model没有
}
}

21.5项目目录结构

image-20240325120511982

22.war包部署

22.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
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
a.执行项目打包的方式为 "war",但默认创建springboot项目打包都是jar
因此需要修改项目的pom.xml:
添加:<packaging>war<packaging>


b.去除springboot项目内嵌tomcat依赖
<!--tomcat解析jsp-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<!--provided当前idea环境可用,打包不参与打包-->
<scope>provided</scope>
</dependency>

<!--去掉内嵌的tomcat依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>


c.在pom.xml的插件(plugins)中配置入口类
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--配置springboot入口类-->
<configuration>
<fork>true</fork>
<!--增加jvm参数-->
<jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
<!--指定入口类-->
<mainClass>com.baizhi.SpringbootDay6Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>


d.启动类添加配置
//1.继承SpringBootServletInitializer 不再使用内嵌容器启动,使用外部tomcat容器启动
//2.覆盖configure方法
public class Application extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
//配置入口类是谁
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}
e.安装jdk、安装tomcat、安装数据库、将打包好的war复制到tomcat webapps目录下 ./startup.sh启动

注:一旦使用外部的tomcat部署application对内嵌服务器做的配置就失效了

22.2 打包部署具体配置

pom.xml设置打包方式为war:

1
2
3
4
5
6
7
8
9
10
<groupId>com.study</groupId>
<artifactId>spring-boot-day6</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-day6</name>
<!--指定war包部署-->
<packaging>war</packaging>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>

在pom.xml的插件(plugin)中指定入口类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--配置springboot入口类-->
<configuration>
<fork>true</fork>
<!--增加jvm参数-->
<jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
<!--指定入口类-->
<mainClass>com.study.SpringBootDay6Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>

排除内嵌的tomcat:pom.xml中修改依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--tomcat解析jsp-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<!--provided表示当前idea环境可用,打包时不参与打包-->
<scope>provided</scope>
</dependency>

<!--去掉内嵌的tomcat依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<!--当前idea环境可用,但打包时不参与打包-->
<scope>provided</scope>
</dependency>

配置入口类XxxApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

@SpringBootApplication
//继承SpringBootServletInitializer:不再使用内嵌tomcat容器启动,使用外部tomcat容器启动
public class SpringBootDay6Application extends SpringBootServletInitializer {

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

//重写configure方法
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringBootDay6Application.class);
}
}

22.3 打包测试

1
2
3
# 一旦使用war部署注意:
- 1. application.yml中配置的port context-path失效
- 2. 访问时使用打成war包的名字和外部tomcat端口号进行访问项目

打包顺序如下:

image-20240325120950101

打包报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
D:\Software_Development\JDK\bin\java.exe -Dmaven.multiModuleProjectDirectory=D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day6 "-Dmaven.home=C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\plugins\maven\lib\maven3" "-Dclassworlds.conf=C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\plugins\maven\lib\maven3\bin\m2.conf" "-Dmaven.ext.class.path=C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\plugins\maven\lib\maven-event-listener.jar" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\lib\idea_rt.jar=56634:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\plugins\maven\lib\maven3\boot\plexus-classworlds-2.6.0.jar" org.codehaus.classworlds.Launcher -Didea.version2020.1 -DskipTests=true package
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.study:spring-boot-day6 >---------------------
[INFO] Building spring-boot-day6 0.0.1-SNAPSHOT
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ spring-boot-day6 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.681 s
[INFO] Finished at: 2022-06-04T17:29:55+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources) on project spring-boot-day6: Input length = 1 -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionExceptio

通过上面报错信息可以发现可能是版本问题(3.2.0),解决办法参考链接:Failed to execute goal org.apache.maven.plugins:maven-resour_二炮的博客-CSDN博客,修改版本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--SpringBoot打包依赖-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

<!--在这里修改版本-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.3</version>
</plugin>
<!---->
</plugins>
</build>

重新打包,编译信息如下:

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
D:\Software_Development\JDK\bin\java.exe -Dmaven.multiModuleProjectDirectory=D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day6 "-Dmaven.home=C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\plugins\maven\lib\maven3" "-Dclassworlds.conf=C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\plugins\maven\lib\maven3\bin\m2.conf" "-Dmaven.ext.class.path=C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\plugins\maven\lib\maven-event-listener.jar" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\lib\idea_rt.jar=57250:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\JetBrains\IntelliJ IDEA 2020.1\plugins\maven\lib\maven3\boot\plexus-classworlds-2.6.0.jar" org.codehaus.classworlds.Launcher -Didea.version2020.1 -DskipTests=true package
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.study:spring-boot-day6 >---------------------
[INFO] Building spring-boot-day6 0.0.1-SNAPSHOT
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ spring-boot-day6 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ spring-boot-day6 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 6 source files to D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day6\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ spring-boot-day6 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day6\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.10.1:testCompile (default-testCompile) @ spring-boot-day6 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day6\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ spring-boot-day6 ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-war-plugin:3.3.2:war (default-war) @ spring-boot-day6 ---
[INFO] Packaging webapp
[INFO] Assembling webapp [spring-boot-day6] in [D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day6\target\spring-boot-day6-0.0.1-SNAPSHOT]
[INFO] Processing war project
[INFO] Copying webapp resources [D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day6\src\main\webapp]
[INFO] Building war: D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day6\target\spring-boot-day6-0.0.1-SNAPSHOT.war
[INFO]
[INFO] --- spring-boot-maven-plugin:2.7.0:repackage (repackage) @ spring-boot-day6 ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16.423 s
[INFO] Finished at: 2022-06-04T17:58:56+08:00
[INFO] -----------------------------------------------------------------------

出现上述提示信息,表示项目打包成功,生成的war包如下:

image-20240325121137677

22.4 部署

1.Linux
安装jdk、安装tomcat、安装数据库、将打包好的war复制到tomcat webapps目录下
双击startup.sh启动

2.Windows
安装jdk和数据库后,将打包好的war复制到tomcat webapps目录下
双击startup.bat启动 可以在命令行启动同时开启日志监听查看

此处以Windows为例进行说明(没有连接数据库):

(1)将打包好的war包重命名为springboot.war,复制到tomcat/webapps目录下

image-20240325121227951

(2)双击bin目录下的startup.bat运行项目

image-20240325121249712

image-20240325121303685

出现上面错误是因为8080端口被占用,解决方案参考链接:Tomcat 8080 端口被占用解决方案_Wbbp的博客-CSDN博客_tomcat8080端口被占用怎么解决可以在conf/server.xml中修改端口号:

image-20240325121340724

image-20240325121349569

保存修改后,继续双击startup.bat启动

image-20240325121404157

出现上述提示,表示项目启动成功。

打开浏览器,访问地址:http://127.0.0.1:8888/springboot/interceptor/test

或者 http://127.0.0.1:8888/springboot/download.jsp 可以访问成功,表示项目部署成功!

23.jar包部署

23.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
31
32
33
34
35
36
37
38
39
40
41
42
43
a.<packaging>jar</packaging>  默认就是部署方式就是ja包部署

b.在target目录中获取对应打包好的jar文件

c.安装jdk 数据库

d.启动jar包
java -jar 对应jar文件名字 nohup &(其中,nohup &表示后台直接启动,不要日志)

e.注意事项:
1.springboot项目在使用jsp模板时,jar包部署默认无法找到jsp页面,解决办法:修改插件版本为1.4.2,注意只有这唯一一个版本可用
<plugins>
<!--版本必须为1.4.2版本-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.4.2.RELEASE</version>
</plugin>
</plugins>

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>

f.重新打包测试

23.2打包部署具体配置

a.pom.xml设置打包方式为jar

1
2
3
4
5
6
7
8
9
10
<groupId>com.study</groupId>
<artifactId>spring-boot-day5</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-day5</name>
<!--指定jar包部署,此处不写,直接默认也是jar包部署-->
<packaging>jar</packaging>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>

注意:默认方式也是jar

b.执行打包

image-20240325121623081

d.控制台命令窗口运行项目

image-20240325121652202

e.测试访问:http://localhost:8888/spring-boot-day5/upload.jsp

image-20240325121715840

f.修改插件版本:在项目的pom.xml配置文件中build标签中修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<build>
<plugins>
<!--jar包打包maven-plugin版本必须为1.4.2版本-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.4.2.RELEASE</version>
</plugin>

<!--在这里修改maven-resources-plugin版本 能够将Maven项目中的各种资源文件复制到指定的输出目录中-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.3</version>
</plugin>
</plugins>
</build>

h.指定jsp打包配置:在项目的pom.xml配置文件中build标签中加入resources配置

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
<build>
<plugins>
<!--jar包打包maven-plugin版本必须为1.4.2版本-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.4.2.RELEASE</version>
</plugin>

<!--在这里修改maven-resources-plugin版本-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.3</version>
</plugin>
</plugins>

<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>

重新打包部署测试即可!

重新clean报错:

Failed to execute goal org.apache.maven.plugins:maven-clean-plugin:3.2.0:clean (default-clean) on project spring-boot-day5: Failed to clean project: Failed to delete D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day5\target\spring-boot-day5-0.0.1-SNAPSHOT.jar
原因:jar包正在被占用,无法使用,需要在资源任务管理器关闭Java(TM) Platform SE binary,再重新clean即可

image-20240325121852849

image-20240325121906603

安装jdk 数据库

Windows控制台部署运行:

image-20240325121920834

打开浏览器访问路径:http://localhost:8888/spring-boot-day5/upload.jsp

image-20240325122021658

上传文件后:

image-20240325122038003

以上均测试成功,表示项目部署成功!

24.Thymeleaf

Thymeleaf是一个用于web和独立环境的现代服务器端Java模板引擎。Jsp也是Java模板引擎,Thymeleaf完全可以用来替代Jsp,在使用时Jsp和Thymeleaf不要混用-摘自官网Thymeleaf

Thymeleaf是跟Velocity、FreeMarker类似的模板引擎,它可以完全替代JSP,相较与其他的模板引擎相比, Thymeleaf在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。

注意:Thymeleaf使用时必须要经过controller跳转,不能直接访问,直接访问不能进行动态数据渲染。

24.1集成Thymeleaf模板

pom.xml引入依赖

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

application.yml编写配置

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 8888
servlet:
context-path: /spring-boot-day7

# 配置Thymeleaf 可以不配 所有配置都是默认的
spring:
thymeleaf:
prefix: classpath:/templates/ # 指定Thymeleaf前缀目录
suffix: .html # 指定模板后缀
cache: true #是否开启Thymeleaf缓存,默认是true开启缓存
# 在开发过程中推荐使用false(类似jsp加载)修改完会立即生效,不需要每次重启springboot
# idea还要做配置 编辑配置 onupdate action和on frame deactivation:update resources

在templates目录中定义模板index.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试SpringBoot与Thymeleaf的集成</title>
</head>
<body>
<h1>Hello,Thymeleaf!</h1>
</body>
</html>

测试路径:直接访问根路径默认定位到templates/index.html 但我们不建议这么做 建议经过控制器

http://localhost:8888/spring-boot-day7/

image-20240325123002563

直接访问http://localhost:8888/spring-boot-day7/index.html则不行

编写控制器测试

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

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

/**
* @ClassName HelloController
* @Description TODO
* @Author chabai
* @Date 2022/6/11 15:59
* @Version 1.0
*/
@Controller //一定要是@Controller,不能再使用@RestController注解
@RequestMapping("hello")
public class HelloController {
@RequestMapping("hello")
public String hello(){
System.out.println("测试springboot与Thymeleaf的集成");
return "index";// templates/index.html,最终封装成index.html进行返回 forward跳转
// 直接访问根路径(http://localhost:8888/spring-boot-day7/)加载出index.html,但无渲染效果
}
}

http://localhost:8888/spring-boot-day7/hello/hello

image-20240325123021847

24.2模板基本语法

html使用Thymeleaf相关语法必须导入thymeleaf的头

使用Thymeleaf时必须在html页面中加入唯一的命名空间(namespace)才能使用相关语法:Thymeleaf简写th,在xmlns:名字处写th

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

注意:有的插件可以导入/提示命名空间!

初始的demo.html页面 用于页面展示

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用来测试Thymeleaf语法</title>
</head>
<body>
<h1>测试Thymeleaf语法基本使用</h1>
</body>
</html>

新建控制器DemoController

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
package com.study.controller;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
* @ClassName DemoController
* @Description TODO
* @Author chabai
* @Date 2022/6/13 20:36
* @Version 1.0
*/
@Controller
@RequestMapping("demo")
public class DemoController {
@RequestMapping("demo")
public String demo(HttpServletRequest request, Model model, HttpSession session){
System.out.println("demo ok!");
return "demo"; //templates/demo.html,最终封装成demo.html进行返回
}
}

测试路径:http://localhost:8888/spring-boot-day7/demo/demo

image-20240325123221742

24.3获取单个类型数据 String Integer

DemoController

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
package com.study.controller;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
* @ClassName DemoController
* @Description TODO
* @Author chabai
* @Date 2022/6/13 20:36
* @Version 1.0
*/
@Controller
@RequestMapping("demo")
public class DemoController {
@RequestMapping("demo")
//model底层封装的也是request作用域
public String demo(HttpServletRequest request, Model model, HttpSession session){
System.out.println("demo ok!");
/**
* 1.传递单个数据:String、Integer、解析超链接
*/ //目标 controller里茶白--》传递到页面并展示出来
String name = "茶白"; //设值
request.setAttribute("name",name); //设置键值存入作用域 然后才能在页面去取
Integer age = 27;
model.addAttribute("age",age);
String content = "<a href='http://www.baidu.com'>百度一下</a>";
model.addAttribute("content",content);
return "demo";
}
}

demo.html

1
2
3
4
5
6
7
8
9
10
11
<h2>1.获取单个数据</h2>
<!--
th:text="${属性名}"获取request作用域数据:直接将获取到的数据以文本形式渲染到页面中
th:utext="${属性名}"获取request作用域数据:先将获取到的数据解析成html标签,再以文本形式渲染到页面中
-->
<h3>String类型数据:<span th:text="${name}"/></h3>
<h3>Integer类型数据:<span th:text="${age}"/></h3>
<h3>超链接:<span th:utext="${content}"/></h3>
<h3>输入框:</h3>
<input type="text" name="username" th:value="${name}">
<input type="text" name="age" th:value="${age}">

注:th:text=可以放在任何html标签,但注意位置 。 th除了可以加在th:text|th:utext还可以加在html标签的任意属性上代表在这个属性去使用Thymeleaf语法

24.4获取对象类型数据

新建对象User

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
package com.study.entity;

import java.util.Date;

/**
* @ClassName User
* @Description TODO
* @Author chabai
* @Date 2022/6/13 20:33
* @Version 1.0
*/
public class User {
private Integer id;
private String name;
private Double salary;
private Date birthday;

public User() {
}

public User(Integer id, String name, Double salary, Date birthday) {
this.id = id;
this.name = name;
this.salary = salary;
this.birthday = birthday;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", salary=" + salary +
", birthday=" + birthday +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Double getSalary() {
return salary;
}

public void setSalary(Double salary) {
this.salary = salary;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}

DemoController

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
package com.study.controller;

import com.study.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
* @ClassName DemoController
* @Description TODO
* @Author chabai
* @Date 2022/6/13 20:36
* @Version 1.0
*/
@Controller
@RequestMapping("demo")
public class DemoController {
@RequestMapping("demo")
//model底层封装的也是request作用域
public String demo(HttpServletRequest request, Model model, HttpSession session){
System.out.println("demo ok!");
/**
* 2.传递对象类型数据
*/
User user = new User(1,"茶白",1234.56,new Date());
model.addAttribute("user",user);
return "demo";
}
}

demo.html

1
2
3
4
5
6
7
8
<h2>2.获取对象类型数据</h2>
<h3>
user.id:<span th:text="${user.id}"/><br>
user.name:<span th:text="${user.name}"/><br>
user.salary:<span th:text="${user.salary}"/><br>
<!--格式化日期:${#dates.format(格式化值,'格式化的格式')}},其中,dates.format为内置函数-->
user.birthday:<span th:text="${#dates.format(user.birthday,'yyyy-MM-dd HH:mm:ss')}"/>
</h3>

注:设置的键才是我们作用域放入的名字

24.5获取集合类型数据

DemoController

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
package com.study.controller;

import com.study.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
* @ClassName DemoController
* @Description TODO
* @Author chabai
* @Date 2022/6/13 20:36
* @Version 1.0
*/
@Controller
@RequestMapping("demo")
public class DemoController {
@RequestMapping("demo")
//model底层封装的也是request作用域
public String demo(HttpServletRequest request, Model model, HttpSession session){
System.out.println("demo ok!");
/**
* 3.传递集合类型数据
*/
List<User> users = Arrays.asList(
new User(11,"熊大",1111.11,new Date()),
new User(22,"熊二",2222.22,new Date()),
new User(33,"光头强",3333.33,new Date())
);
model.addAttribute("users",users);
return "demo";
}
}

demo.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h2>3.获取集合类型数据</h2>
<ul>
<!--遍历数据:th:each="变量(current_element当前遍历元素),变量(state遍历状态对象):集合"-->
<li th:each="user,state:${users}">
state count: <span th:text="${state.count}"></span>
state odd: <span th:text="${state.odd}"></span>
state even: <span th:text="${state.even}"></span>
state size: <span th:text="${state.size}"></span>
id: <span th:text="${user.id}"></span>
name: <span th:text="${user.name}"></span>
salary: <span th:text="${user.salary}"></span>
birthday: <span th:text="${#dates.format(user.birthday,'yyyy年MM月dd日')}"></span>
</li>
</ul>

补充:展示多条数据

直接遍历集合

1
2
3
4
5
6
7
 
<ul th:each="user:${users}">
<li th:text="${user.id}"></li>
<li th:text="${user.name}"></li>
<li th:text="${user.age}"></li>
<li th:text="${#dates.format(user.bir,'yyyy-MM-dd')}"></li>
</ul>

遍历时获取遍历状态

1
2
3
4
5
6
7
 
<ul th:each="user,userStat:${users}">
<li><span th:text="${userStat.count}"/>-<span th:text="${user.id}"/></li> 获取遍历次数 count 从1开始 index 从0开始
<li><span th:text="${userStat.odd}"/>-<span th:text="${user.name}"/></li> 获取当前遍历是否是奇数行
<li><span th:text="${userStat.even}"/>-<span th:text="${user.age}"/></li> 获取当前遍历是否是偶数行
<li><span th:text="${userStat.size}"/>-<span th:text="${user.bir}"/></li> 获取当前集合的总条数
</ul>

24.6有条件的显示数据 判断

demo.html

1
2
3
4
5
6
7
8
<h2>4.有条件的展示数据</h2>
<!-- th:if="${age>=25}" 作用:有条件的显示数据-->
<div style="width: 100px;height: 100px;background: red" th:if="${age>25}">
我是小红
</div>
<div style="width: 100px;height: 100px;background: blue" th:if="${age<=25}">
我是小蓝
</div>

补充运算符:

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

上边是forward作用域传递数据


24.7重定向传递session作用域数据

1
2
3
# 传递数据和跳转方式有关 
- forward跳转: request | model 作用域
- redirect跳转: session作用域传递数据

DemoControlleer

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
package com.study.controller;

import com.study.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
* @ClassName DemoController
* @Description TODO
* @Author chabai
* @Date 2022/6/13 20:36
* @Version 1.0
*/
@Controller
@RequestMapping("demo")
public class DemoController {
@RequestMapping("demo")
//model底层封装的也是request作用域
public String demo(HttpServletRequest request, Model model, HttpSession session){
System.out.println("demo ok!");
/**
* 5.向session作用域存储数据
*/
session.setAttribute("username","猪猪侠");
return "demo";
}
}

demo.html

1
2
<h2>5.获取session作用域数据 session.key application.key</h2>
<span th:text="${session.username}"></span>

访问路径:http://localhost:8888/spring-boot-day7/demo/demo

image-20240325123838366

1
2
3
4
# 总结
1.使用 th:text="${属性名}" 获取对应数据,获取数据时会将对应标签中数据清空,因此最好是空标签
2.使用 th:utext="${属性名}" 获取对应的数据,可以将数据中html先解析在渲染到页面
3.使用 th:value="${属性名}" 获取数据直接作为表单元素value属性

24.8引入静态资源

使用thymeleaf模板时项目中的静态资源默认放在resources路径下的static目录中

demo.css

1
2
3
h2{
background: yellow;
}

demo.js

1
2
3
function test() {
alert('test');
}

demo.html

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
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用来测试Thymeleaf语法</title>

<!--th:href="@{/}",其中,@{/}用来获取应用项目名称-->

<!--引入css资源 默认会去classpath:static找 不用配置-->
<link rel="stylesheet" th:href="@{/demo.css}">

<!--引入js资源-->
<script th:src="@{/demo.js}"></script>
<script>
test();
/**
* 通过js代码获取应用名:
* 通过Thymeleaf语法获取项目名,使用Thymeleaf内联表达式 [[thymeleaf]]
*/
let contentPath = "[[@{/}]]";
console.log("项目名",contentPath);
</script>
</head>
<body>
<h1>测试Thymeleaf语法基本使用</h1>
<h2>1.获取单个数据</h2>
<!--
th:text="${属性名}"获取request作用域数据:直接将获取到的数据以文本形式渲染到页面中
th:utext="${属性名}"获取request作用域数据:先将获取到的数据解析成html标签,再以文本形式渲染到页面中
-->
String类型数据:<span th:text="${name}"/><br>
Integer类型数据:<span th:text="${age}"/><br>
超链接:<span th:utext="${content}"/><br>
输入框:<br>
<input type="text" name="username" th:value="${name}"><br>
<input type="text" name="age" th:value="${age}">


<h2>2.获取对象类型数据</h2>
user.id:<span th:text="${user.id}"/><br>
user.name:<span th:text="${user.name}"/><br>
user.salary:<span th:text="${user.salary}"/><br>
<!--格式化日期:${#dates.format(格式化值,'格式化的格式')}},其中,dates.format为内置函数-->
user.birthday:<span th:text="${#dates.format(user.birthday,'yyyy-MM-dd HH:mm:ss')}"/>

<h2>3.获取集合类型数据</h2>
<ul>
<!--遍历数据:th:each="变量(current_element当前遍历元素),变量(state遍历状态对象):集合"-->
<li th:each="user,state:${users}">
state count: <span th:text="${state.count}"></span>
state odd: <span th:text="${state.odd}"></span>
state even: <span th:text="${state.even}"></span>
state size: <span th:text="${state.size}"></span>
id: <span th:text="${user.id}"></span>
name: <span th:text="${user.name}"></span>
salary: <span th:text="${user.salary}"></span>
birthday: <span th:text="${#dates.format(user.birthday,'yyyy年MM月dd日')}"></span>
</li>
</ul>

<h2>4.有条件的展示数据</h2>
<!-- th:if="${age>=25}" 作用:有条件的显示数据-->
<div style="width: 100px;height: 100px;background: red" th:if="${age>25}">
我是小红
</div>
<div style="width: 100px;height: 100px;background: blue" th:if="${age<=25}">
我是小蓝
</div>

<h2>5.获取session作用域数据</h2>
<span th:text="${session.username}"></span>
</body>
</html>

或者

1
2
<link rel="stylesheet" th:href="@{/css/index.css}">
<script th:src="@{/js/jquery-min.js}"></script>

注意: @{/}代表通过thymeleaf语法动态获取应用名

在js代码中获取项目名:

1
2
3
<script>
const ctx = '[[@{/}]]';
</script>

注意:[[书写thymeleaf语法]],这里[[ ]]是thymeleaf内嵌表达式。

最终效果:

image-20240325124218687

24.9最终项目结构

image-20240325124236040

25.Thymeleaf测试案例

25.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
需求分析: 分析这个项目含有哪些功能模块
用户模块:
注册
登录
验证码
安全退出
真实用户
员工模块:
添加员工+上传头像
展示员工列表+展示员工头像
删除员工信息+删除员工头像
更新员工信息+更新员工头像

库表设计(概要设计): 1.分析系统有哪些表 2.分析表与表关系 3.确定表中字段(显性字段 隐性字段(业务字段))
2张表
1.用户表 user
id username realname password gender
2.员工表 employee
id name salary birthday photo
创建一个库: ems-thymeleaf

详细设计:
验证库表正确性
省略

编码(环境搭建+业务代码开发)
1.创建一个springboot项目 项目名字: ems-thymeleaf
2.修改配置文件为 application.yml pom.xml 2.5.0
3.修改端口 8888 项目名: ems-thymeleaf
4.springboot整合thymeleaf使用
a.引入依赖:thymeleaf
b.配置文件中指定thymeleaf相关配置
c.编写控制器测试
5.springboot整合mybatis
a.引入依赖:mysql、druid、mybatis-springboot-stater
b.配置文件中
6.导入项目页面
static 存放静态资源
templates 目录 存放模板文件

测试
上线部署
维护
发版
======================================================================

用户相关:
1.登录
2.注册
3.验证码
1.验证码工具类

25.2 springboot整合thymeleaf

pom.xml引入thymeleaf依赖

1
2
3
4
5
6
<!--引入Thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.7.0</version>
</dependency>

修改application.properties为application.yml,添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
# 公共配置
server:
port: 8888
servlet:
context-path: /ems-thymeleaf

# Thymeleaf配置
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html

新建demo.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en" xmlns="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试Thymeleaf</title>
</head>
<body>
<h1>测试Thymeleaf</h1>
<h4>获取数据:<span th:text="${msg}"></span></h4>
</body>
</html>

DemoController

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.study.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @ClassName DemoController
* @Description TODO
* @Author chabai
* @Date 2022/6/27 9:45
* @Version 1.0
*/
@Controller
@RequestMapping("demo")
public class DemoController {
private static final Logger log = LoggerFactory.getLogger(DemoController.class);

@RequestMapping("demo")
public String demo(Model model){
log.debug("demo ok");
model.addAttribute("msg","hello,Thymeleaf!");
return "demo";
}
}

启动项目,访问路径:http://localhost:8888/ems-thymeleaf/demo/demo

image-20240325124918013

25.3 springboot整合mybatis

pom.xml引入依赖:mysql、druid、mybatis-spring-boot-starter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>

<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>

<!--mybatis-spring-boot-starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>

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
# 公共配置
server:
port: 8888
servlet:
context-path: /ems-thymeleaf

# Thymeleaf配置
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html

# 配置数据源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ems-thymeleaf?characterEncoding=UTF-8
username: root
password: 123456

# 配置mybatis
mybatis:
mapper-locations: classpath:com/study/mapper/*.xml
type-aliases-package: com.study.entity

# 配置日志
logging:
level:
root: info
com.study: debug

启动类添加注解扫描

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

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.study.dao")
public class EmsThymeleafApplication {

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

重新启动一下项目,运行不报错即可。

25.4 导入项目页面(静态资源)

在static目录下导入css、img、js等静态资源,导入之后Rebuild一下项目

image-20240325125112486

新建MVC配置类:不用单独开发controller来访问html页面

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.study.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @ClassName MvcConfig
* @Description TODO
* @Author chabai
* @Date 2022/6/27 10:35
* @Version 1.0
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
//通过在这里面配置,不需要再为每一个访问thymeleaf模板页面单独开发一个controller请求了
public void addViewControllers(ViewControllerRegistry registry) {
//ViewController: 请求路径,ViewName: 跳转视图
registry.addViewController("login").setViewName("login");
registry.addViewController("register").setViewName("regist");//注意与HTML页面对应
}
}

启动项目,访问页面:

(1)注册页面:http://localhost:8888/ems-thymeleaf/register

image-20240325125210111

(2)登录页面:http://localhost:8888/ems-thymeleaf/login

image-20240325125223809

25.5 验证码

验证码工具类

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package com.study.utils;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Random;

/**
*@创建人 cx
*@创建时间 2018/11/27 17:36
*@描述 验证码生成
*/
public class VerifyCodeUtils{
//使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
private static Random random = new Random();

/**
* 使用系统默认字符源生成验证码
* @param verifySize 验证码长度
* @return
*/
public static String generateVerifyCode(int verifySize){
return generateVerifyCode(verifySize, VERIFY_CODES);
}
/**
* 使用指定源生成验证码
* @param verifySize 验证码长度
* @param sources 验证码字符源
* @return
*/
public static String generateVerifyCode(int verifySize, String sources){
if(sources == null || sources.length() == 0){
sources = VERIFY_CODES;
}
int codesLen = sources.length();
Random rand = new Random(System.currentTimeMillis());
StringBuilder verifyCode = new StringBuilder(verifySize);
for(int i = 0; i < verifySize; i++){
verifyCode.append(sources.charAt(rand.nextInt(codesLen-1)));
}
return verifyCode.toString();
}

/**
* 生成随机验证码文件,并返回验证码值
* @param w
* @param h
* @param outputFile
* @param verifySize
* @return
* @throws IOException
*/
public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException{
String verifyCode = generateVerifyCode(verifySize);
outputImage(w, h, outputFile, verifyCode);
return verifyCode;
}

/**
* 输出随机验证码图片流,并返回验证码值
* @param w
* @param h
* @param os
* @param verifySize
* @return
* @throws IOException
*/
public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException{
String verifyCode = generateVerifyCode(verifySize);
outputImage(w, h, os, verifyCode);
return verifyCode;
}

/**
* 生成指定验证码图像文件
* @param w
* @param h
* @param outputFile
* @param code
* @throws IOException
*/
public static void outputImage(int w, int h, File outputFile, String code) throws IOException{
if(outputFile == null){
return;
}
File dir = outputFile.getParentFile();
if(!dir.exists()){
dir.mkdirs();
}
try{
outputFile.createNewFile();
FileOutputStream fos = new FileOutputStream(outputFile);
outputImage(w, h, fos, code);
fos.close();
} catch(IOException e){
throw e;
}
}

/**
* 输出指定验证码图片流
* @param w
* @param h
* @param os
* @param code
* @throws IOException
*/
public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{
int verifySize = code.length();
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Random rand = new Random();
Graphics2D g2 = image.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
Color[] colors = new Color[5];
Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN,
Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.YELLOW };
float[] fractions = new float[colors.length];
for(int i = 0; i < colors.length; i++){
colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
fractions[i] = rand.nextFloat();
}
Arrays.sort(fractions);
g2.setColor(Color.GRAY);// 设置边框色
g2.fillRect(0, 0, w, h);
Color c = getRandColor(200, 250);
g2.setColor(c);// 设置背景色
g2.fillRect(0, 2, w, h-4);
//绘制干扰线
Random random = new Random();
g2.setColor(getRandColor(160, 200));// 设置线条的颜色
for (int i = 0; i < 20; i++) {
int x = random.nextInt(w - 1);
int y = random.nextInt(h - 1);
int xl = random.nextInt(6) + 1;
int yl = random.nextInt(12) + 1;
g2.drawLine(x, y, x + xl + 40, y + yl + 20);
}

// 添加噪点
float yawpRate = 0.05f;// 噪声率
int area = (int) (yawpRate * w * h);
for (int i = 0; i < area; i++) {
int x = random.nextInt(w);
int y = random.nextInt(h);
int rgb = getRandomIntColor();
image.setRGB(x, y, rgb);
}
shear(g2, w, h, c);// 使图片扭曲
g2.setColor(getRandColor(100, 160));
int fontSize = h-4;
Font font = new Font("Algerian", Font.ITALIC, fontSize);
g2.setFont(font);
char[] chars = code.toCharArray();
for(int i = 0; i < verifySize; i++){
AffineTransform affine = new AffineTransform();
affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2);
g2.setTransform(affine);
g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10);
}
g2.dispose();
ImageIO.write(image, "jpg", os);
}

private static Color getRandColor(int fc, int bc) {
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}

private static int getRandomIntColor() {
int[] rgb = getRandomRgb();
int color = 0;
for (int c : rgb) {
color = color << 8;
color = color | c;
}
return color;
}

private static int[] getRandomRgb() {
int[] rgb = new int[3];
for (int i = 0; i < 3; i++) {
rgb[i] = random.nextInt(255);
}
return rgb;
}

private static void shear(Graphics g, int w1, int h1, Color color) {
shearX(g, w1, h1, color);
shearY(g, w1, h1, color);
}

private static void shearX(Graphics g, int w1, int h1, Color color) {
int period = random.nextInt(2);
boolean borderGap = true;
int frames = 1;
int phase = random.nextInt(2);
for (int i = 0; i < h1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(0, i, w1, 1, (int) d, 0);
if (borderGap) {
g.setColor(color);
g.drawLine((int) d, i, 0, i);
g.drawLine((int) d + w1, i, w1, i);
}
}

}

private static void shearY(Graphics g, int w1, int h1, Color color) {
int period = random.nextInt(40) + 10; // 50;
boolean borderGap = true;
int frames = 20;
int phase = 7;
for (int i = 0; i < w1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(i, 0, 1, h1, 0, (int) d);
if (borderGap) {
g.setColor(color);
g.drawLine(i, (int) d, i, 0);
g.drawLine(i, (int) d + h1, i, h1);
}
}
}
}

添加控制器UserController

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
package com.study.controller;

import com.study.utils.VerifyCodeUtils;
import com.sun.deploy.net.HttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
* @ClassName UserController
* @Description TODO
* @Author chabai
* @Date 2022/6/27 11:53
* @Version 1.0
*/
@Controller
@RequestMapping("user")
public class UserController {
/**
* @MethodName generateImageCode
* @Description 生成图片验证码
* @param: session
* @param: response
* @Author chabai
* @Date 2022/6/27 12:08
*/
@RequestMapping("generateImageCode")
public void generateImageCode(HttpSession session, HttpServletResponse response) throws IOException {
//1.生成4位随机数
String code = VerifyCodeUtils.generateVerifyCode(4);
//2.保存到session作用域
session.setAttribute("code",code);
//3.设置响应类型
response.setContentType("image/png");
//4.获得输出流
ServletOutputStream sos = response.getOutputStream();
//5.根据随机数生成图片
VerifyCodeUtils.outputImage(220,60,sos,code);
}
}

register.html

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
94
95
96
97
98
99
100
101
102
 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>regist</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}" />
</head>
<body>
<div id="wrap">
<div id="top_content">
<div id="header">
<div id="rightheader">
<p>
2009/11/20
<br />
</p>
</div>
<div id="topheader">
<h1 id="title">
<a href="#">main</a>
</h1>
</div>
<div id="navigation">
</div>
</div>
<div id="content">
<p id="whereami">
</p>
<h1>
注册
</h1>
<form th:action="@{/user/register}" method="post">
<table cellpadding="0" cellspacing="0" border="0"
class="form_table">
<tr>
<td valign="middle" align="right">
用户名:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="username" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
真实姓名:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="realname" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
密码:
</td>
<td valign="middle" align="left">
<input type="password" class="inputgri" name="password" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
性别:
</td>
<td valign="middle" align="left">

<input type="radio" class="inputgri" name="gender" value="1" checked="checked"/>

<input type="radio" class="inputgri" name="gender" value="0"/>
</td>
</tr>

<tr>
<td valign="middle" align="right">
验证码:
<img id="num" th:src="@{/user/generateImageCode}" />
<a href="javascript:;" οnclick="changeImageCode()">换一张</a>
<script>
function changeImageCode(){
document.getElementById('num').src = '[[@{/user/generateImageCode}]]?'+(new Date()).getTime()
}
</script>
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="code" />
</td>
</tr>
</table>
<p>
<input type="submit" class="button" value="立即注册 &raquo;" />
<a href="login.html">已有账号,返回登录</a>
</p>
</form>
</div>
</div>
<div id="footer">
<div id="footer_bg">
ABC@126.com
</div>
</div>
</div>
</body>
</html>

测试路径

(1)http://localhost:8888/ems-thymeleaf/user/generateImageCode

image-20240325125439743

(2)http://localhost:8888/ems-thymeleaf/register

image-20240325125453435

25.6 用户注册

创建数据库ems-thymeleaf、表user

1
2
3
4
5
6
7
8
9
10
create database if not exists `ems-thymeleaf`;
use `ems-thymeleaf`;
create table if not exists `user`(
id int(20) not null auto_increment comment '用户ID',
username varchar(100) not null comment '用户名',
realname varchar(100) comment '真实姓名',
password varchar(100) comment '密码',
gender boolean comment '性别',
primary key(id)
)engine=innodb charset=utf8;

新建实体类User

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
 
package com.study.entity;

/**
* @ClassName User
* @Description TODO
* @Author chabai
* @Date 2022/6/27 15:49
* @Version 1.0
*/
public class User {
private Integer id;
private String username;
private String realname;
private String password;
private Boolean gender;

public User() {
}

public User(Integer id, String username, String realname, String password, Boolean gender) {
this.id = id;
this.username = username;
this.realname = realname;
this.password = password;
this.gender = gender;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", realname='" + realname + '\'' +
", password='" + password + '\'' +
", gender=" + gender +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getRealname() {
return realname;
}

public void setRealname(String realname) {
this.realname = realname;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public Boolean getGender() {
return gender;
}

public void setGender(Boolean gender) {
this.gender = gender;
}
}

创建UserDAO

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
package com.study.dao;

import com.study.entity.User;

/**
* @ClassName UserDAO
* @Description TODO
* @Author chabai
* @Date 2022/6/27 16:02
* @Version 1.0
*/
public interface UserDAO {
/**
* @MethodName findByUserName
* @Description 根据用户名查找用户
* @param: username
* @return: com.study.entity.User
* @Author chabai
* @Date 2022/6/27 16:03
*/
User findByUserName(String username);

/**
* @MethodName register
* @Description 用户注册
* @param: user
* @Author chabai
* @Date 2022/6/27 16:03
*/
void register(User user);
}

创建UserDAOMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
<?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.study.dao.UserDAO">
<!--findByUserName-->
<select id="findByUserName" parameterType="String" resultType="User">
select
id,username,realname,password,gender
from `user`
where username=#{username}
</select>

<!--register-->
<insert id="register" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into `user` values(#{id},#{username},#{realname},#{password},#{gender})
</insert>

</mapper>

创建UserService

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

import com.study.entity.User;

/**
* @ClassName UserService
* @Description TODO
* @Author chabai
* @Date 2022/6/27 15:48
* @Version 1.0
*/
public interface UserService {
/**
* @MethodName register
* @Description 用户注册
* @param: user
* @Author chabai
* @Date 2022/6/27 15:50
*/
void register(User user);
}

创建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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.study.service;

import com.study.dao.UserDAO;
import com.study.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;
import java.nio.charset.StandardCharsets;

/**
* @ClassName UserServiceImpl
* @Description TODO
* @Author chabai
* @Date 2022/6/27 16:14
* @Version 1.0
*/
@Service
@Transactional
public class UserServiceImpl implements UserService{
//业务逻辑层调用数据访问层
private UserDAO userDAO;
@Autowired
public UserServiceImpl(UserDAO userDAO) {
this.userDAO = userDAO;
}

/**
* @MethodName register
* @Description
* @param: user
* @Author chabai
* @Date 2022/6/27 16:22
*/
@Override
public void register(User user) {
//1.根据用户名从数据库中查询是否存在已经注册的用户
User userDB = userDAO.findByUserName(user.getUsername());
//2.判断该用户是否存在
if(!ObjectUtils.isEmpty(userDB))
throw new RuntimeException("当前用户已被注册!");//不为空时抛出异常
//3.为空时表明用户不存在,继续注册用户
//对明文密码进行加密:相同字符串多次使用md5加密后结果一致
String newPassword = DigestUtils.md5DigestAsHex(user.getPassword().getBytes(StandardCharsets.UTF_8));
user.setPassword(newPassword);
userDAO.register(user);
}
}

新建UserController

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
package com.study.controller;

import com.study.entity.User;
import com.study.service.UserService;
import com.study.utils.VerifyCodeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
* @ClassName UserController
* @Description TODO
* @Author chabai
* @Date 2022/6/27 11:53
* @Version 1.0
*/
@Controller
@RequestMapping("user")
public class UserController {
//新建日志对象
private static final Logger log = LoggerFactory.getLogger(UserController.class);

/**
* @MethodName generateImageCode
* @Description 生成图片验证码
* @param: session
* @param: response
* @Author chabai
* @Date 2022/6/27 12:08
*/
@RequestMapping("generateImageCode")
public void generateImageCode(HttpSession session, HttpServletResponse response) throws IOException {
//1.生成4位随机数
String code = VerifyCodeUtils.generateVerifyCode(4);
//2.保存到session作用域
session.setAttribute("code",code);
//3.设置响应类型
response.setContentType("image/png");
//4.获得输出流
ServletOutputStream sos = response.getOutputStream();
//5.根据随机数生成图片
VerifyCodeUtils.outputImage(220,60,sos,code);
}

private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}

/**
* @MethodName register
* @Description 用户注册
* @param: user
* @param: code
* @param: session
* @return: java.lang.String
* @Author chabai
* @Date 2022/6/27 15:57
*/
@RequestMapping("register")
public String register(User user,String code,HttpSession session){
log.debug("用户名:{},真实姓名:{},密码:{},性别:{}",
user.getUsername(),user.getRealname(),user.getPassword(),user.getGender());
log.debug("用户输入的验证码: {}",code);

try {
//1.判断用户输入验证码和session中的验证码是否一致
String sessionCode = session.getAttribute("code").toString();
if(!sessionCode.equalsIgnoreCase(code))
throw new RuntimeException("验证码输入错误!");
//2.注册用户
userService.register(user);
} catch (RuntimeException e) {
e.printStackTrace();
return "redirect:/register";//注册失败回到注册
}
return "redirect:/login";//注册成功转到登录
}
}

访问路径:http://localhost:8888/ems-thymeleaf/register,进行注册,成功跳转到登录页面,失败回到注册页面!

25.7 用户登录

在UserService添加登录方法

1
2
3
4
5
6
7
8
9
10
/**
* @MethodName login
* @Description 用户登录
* @param: username
* @param: password
* @return: com.study.entity.User
* @Author chabai
* @Date 2022/6/27 17:02
*/
User login(String username,String password);

在UserServiceImpl实现登录方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @MethodName login
* @Description 用户登录
* @param: username
* @param: password
* @return: com.study.entity.User
* @Author chabai
* @Date 2022/6/27 17:03
*/
@Override
public User login(String username, String password) {
//1.根据用户名查询用户
User user = userDAO.findByUserName(username);
//2.判断用户是否存在,存在执行登录;不存在,抛出异常
if(ObjectUtils.isEmpty(user))
throw new RuntimeException("用户名不存在!");
//3.在用户存在的前提下,比较密码是否正确,正确执行登录;不正确,抛出异常
//密码加密
String passwordSecret = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
if(!user.getPassword().equals(passwordSecret))
throw new RuntimeException("密码错误!");
return user;
}

UserController添加登录方法

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
/**
* @MethodName login
* @Description 用户登录
* @param: username
* @param: password
* @param: session
* @return: java.lang.String
* @Author chabai
* @Date 2022/6/27 17:11
*/
@RequestMapping("login")
public String login(String username,String password,HttpSession session){
log.debug("本次登录的用户名为:{}",username);
log.debug("本次登录的密码为:{}",password);
try {
//1.调用业务逻辑层进行登录
User user = userService.login(username, password);
//2.保存用户信息供页面调用
session.setAttribute("user",user);
} catch (Exception e) {
e.printStackTrace();
return "redirect:/login";//登录失败回到登录页面
}
return "redirect:/employee/lists";//登录成功,跳转到查询所有员工信息控制器路径
}

添加EmpployeeController

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.study.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @ClassName EmployeeController
* @Description TODO
* @Author chabai
* @Date 2022/6/27 17:15
* @Version 1.0
*/
@Controller
@RequestMapping("employee")
public class EmployeeController {
private static final Logger log = LoggerFactory.getLogger(EmployeeController.class);

@RequestMapping("lists")
public String list(Model model){
log.debug("查询所有员工信息");
return "emplist";
}

}

login.html

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
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>login</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css"
href="css/style.css" />
</head>

<body>
<div id="wrap">
<div id="top_content">
<div id="header">
<div id="rightheader">
<p>
2009/11/20
<br />
</p>
</div>
<div id="topheader">
<h1 id="title">
<a href="#">main</a>
</h1>
</div>
<div id="navigation">
</div>
</div>
<div id="content">
<p id="whereami">
</p>
<h1>
欢迎进入,请登录!
</h1>
<form th:action="@{/user/login}" method="post">
<table cellpadding="0" cellspacing="0" border="0"
class="form_table">
<tr>
<td valign="middle" align="right">
用户名:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="username" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
密码:
</td>
<td valign="middle" align="left">
<input type="password" class="inputgri" name="password" />
</td>
</tr>
</table>
<p>
<input type="submit" class="button" value="点我登录 &raquo;" />
&nbsp;&nbsp;
<a href="regist.html">还没有账号,立即注册</a>
</p>
</form>
</div>
</div>
<div id="footer">
<div id="footer_bg">
ABC@126.com
</div>
</div>
</div>
</body>
</html>

emplists.html

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
94
95
96
97
98
99
100
101
102
103
104
105
106
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>emplist</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}" />
</head>
<body>
<div id="wrap">
<div id="top_content">
<div id="header">
<div id="rightheader">
<p>
2009/11/20
<br />
<a th:if="${session.user!=null}" th:href="@{/user/logout}">安全退出</a>
<a th:if="${session.user==null}" th:href="@{/login}">点我登录</a>
</p>
</div>
<div id="topheader">
<h1 id="title">
<a href="#">main</a>
</h1>
</div>
<div id="navigation">
</div>
</div>
<div id="content">
<p id="whereami">
</p>
<h1>
欢迎 <span th:if="${session.user!=null}" th:text="${session.user.username}"></span>
<span th:if="${session.user==null}" >游客</span>!
</h1>
<table class="table">
<tr class="table_header">
<td>
编号
</td>
<td>
姓名
</td>
<td>
头像
</td>
<td>
工资
</td>
<td>
生日
</td>
<td>
操作
</td>
</tr>
<tr th:each="employee,state:${employeeList}" th:class="${state.odd?'row1':'row2'}">
<td>
<span th:text="${employee.id}"></span>
</td>
<td>
<span th:text="${employee.name}"></span>
</td>
<td>
<img th:src="@{/}+${employee.photo}" width="60">
</td>
<td>
<span th:text="${employee.salary}"></span>
</td>
<td>
<span th:text="${#dates.format(employee.birthday,'yyyy/MM/dd')}"></span>
</td>
<td>
<a href="javascript:;" th:οnclick="'deleteEmployee('+${employee.id}+')'">删除</a>
<!-- <a th:href="@{/employee/detail?id=}+${employee.id}+'&name='+${employee.name}">更新</a>-->
<!-- <a th:href="@{/employee/detail(id=${employee.id},name=${employee.name})}">更新</a>-->
<a th:href="@{/employee/detail(id=${employee.id})}">更新</a>

</td>
</tr>
<script>
function deleteEmployee(id){
console.log(id);
if(window.confirm('确定要删除这条记录吗?')){
location.href='[[@{/employee/delete?id=}]]'+id;
}
}
</script>
</table>
<p>
<input type="button" class="button" value="添加" οnclick="addEmp()"/>
<script>
function addEmp(){
location.href = '[[@{/addEmp}]]';
}
</script>
</p>
</div>
</div>
<div id="footer">
<div id="footer_bg">
ABC@126.com
</div>
</div>
</div>
</body>
</html>

测试路径:http://localhost:8888/ems-thymeleaf/login,登录成功跳转到员工列表页面,登录失败回到登录页面

image-20240325130059041

25.8 员工列表

创建employee表

1
2
3
4
5
6
7
8
create table if not exists `employee`(
id int(20) not null auto_increment comment '员工ID',
name varchar(100) not null comment '姓名',
salary double not null comment '工资',
birthday datetime not null comment '生日',
photo varchar(100) not null comment '头像',
primary key(id)
)engine=innodb charset=utf8;

新建实体类Employee

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
  package com.study.entity;

import java.util.Date;

/**
* @ClassName Employee
* @Description TODO
* @Author chabai
* @Date 2022/6/27 18:11
* @Version 1.0
*/
public class Employee {
private Integer id;
private String name;
private Double salary;
private Date birthday;
private String photo;

public Employee() {
}

public Employee(Integer id, String name, Double salary, Date birthday, String photo) {
this.id = id;
this.name = name;
this.salary = salary;
this.birthday = birthday;
this.photo = photo;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Double getSalary() {
return salary;
}

public void setSalary(Double salary) {
this.salary = salary;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getPhoto() {
return photo;
}

public void setPhoto(String photo) {
this.photo = photo;
}

@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", salary=" + salary +
", birthday=" + birthday +
", photo='" + photo + '\'' +
'}';
}
}
————————————————

新建EmployeeDAO

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.study.dao;

import com.study.entity.Employee;

import java.util.List;

/**
* @ClassName EmployeeDAO
* @Description TODO
* @Author chabai
* @Date 2022/6/27 18:23
* @Version 1.0
*/
public interface EmployeeDAO {
/**
* @MethodName lists
* @Description 员工列表
* @return: java.util.List<com.study.entity.Employee>
* @Author chabai
* @Date 2022/6/27 18:24
*/
List<Employee> lists();
}

新建EmployeeDAOMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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.study.dao.EmployeeDAO">
<!--lists-->
<select id="lists" resultType="Employee">
select
id,name,salary,birthday,photo
from employee
</select>
</mapper>

新建EmployeeService

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.study.service;

import com.study.entity.Employee;

import java.util.List;

/**
* @ClassName EmployeeService
* @Description TODO
* @Author chabai
* @Date 2022/6/27 18:26
* @Version 1.0
*/
public interface EmployeeService {
/**
* @MethodName lists
* @Description 员工列表
* @return: java.util.List<com.study.entity.Employee>
* @Author chabai
* @Date 2022/6/27 18:27
*/
List<Employee> lists();
}

新建EmployeeServiceImpl

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
package com.study.service;

import com.study.dao.EmployeeDAO;
import com.study.entity.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
* @ClassName EmployeeServiceImpl
* @Description TODO
* @Author chabai
* @Date 2022/6/27 18:27
* @Version 1.0
*/
@Service
@Transactional
public class EmployeeServiceImpl implements EmployeeService{
//业务逻辑层调用数据访问层
private EmployeeDAO employeeDAO;
@Autowired
public EmployeeServiceImpl(EmployeeDAO employeeDAO) {
this.employeeDAO = employeeDAO;
}

/**
* @MethodName lists
* @Description 员工列表
* @return: java.util.List<com.study.entity.Employee>
* @Author chabai
* @Date 2022/6/27 18:29
*/
@Override
public List<Employee> lists() {
return employeeDAO.lists();
}
}

完善EmployeeController

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
package com.study.controller;

import com.study.entity.Employee;
import com.study.service.EmployeeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

/**
* @ClassName EmployeeController
* @Description TODO
* @Author chabai
* @Date 2022/6/27 17:15
* @Version 1.0
*/
@Controller
@RequestMapping("employee")
public class EmployeeController {
//日志对象
private static final Logger log = LoggerFactory.getLogger(EmployeeController.class);

//控制层调用业务逻辑层
private EmployeeService employeeService;
@Autowired
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}

/**
* @MethodName list
* @Description 员工列表
* @param: model
* @return: java.lang.String
* @Author chabai
* @Date 2022/6/27 18:04
*/
@RequestMapping("lists")
public String list(Model model){
log.debug("查询所有员工信息");
List<Employee> employeeList = employeeService.lists();
model.addAttribute("employeeList",employeeList);
return "emplist";
}
}

测试路径:http://localhost:8888/ems-thymeleaf/employee/lists

image-20240325130429270

25.9 添加员工

完善EmployeDAO

1
2
3
4
5
6
7
8
/**
* @MethodName addEmp
* @Description 保存员工信息
* @param: employee
* @Author chabai
* @Date 2022/6/27 20:11
*/
void addEmp(Employee employee);

完善EmpDAOMapper

1
2
3
4
<!--addEmp-->
<insert id="addEmp" parameterType="Employee" useGeneratedKeys="true" keyProperty="id">
insert into employee values(#{id},#{name},#{salary},#{birthday},#{photo})
</insert>

完善EmployeeService

1
2
3
4
5
6
7
8
/**
* @MethodName addEmp
* @Description
* @param: employee
* @Author chabai
* @Date 2022/6/27 20:12
*/
void addEmp(Employee employee);

完善EmpEmployeeServiceImpl

1
2
3
4
5
6
7
8
9
10
11
/**
* @MethodName addEmp
* @Description 添加员工信息
* @param: employee
* @Author chabai
* @Date 2022/6/27 20:13
*/
@Override
public void addEmp(Employee employee) {
employeeDAO.addEmp(employee);
}

EmployeeController

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
//定义文件路径
@Value("${photo.file.dir}")
private String realpath;

/**
* @MethodName uploadPhoto
* @Description 上传头像
* @param: img
* @param: originalFileName
* @return: java.lang.String
* @Author chabai
* @Date 2022/6/27 20:23
*/
private String uploadPhoto(MultipartFile img,String originalFileName) throws IOException {
String fileNamePrefix = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
String fileNameSuffix = originalFileName.substring(originalFileName.lastIndexOf("."));
String newFileName = fileNamePrefix + fileNameSuffix;
img.transferTo(new File(realpath,newFileName));
return newFileName;
}

/**
* @MethodName addEmp
* @Description 添加员工信息
* @param: employee
* @param: img
* @return: java.lang.String
* @Author chabai
* @Date 2022/6/27 20:15
*/
@RequestMapping("addEmp")
public String addEmp(Employee employee, MultipartFile img) throws IOException {
log.debug("姓名:{},工资:{},生日:{}",employee.getName(),employee.getSalary(),employee.getBirthday());
//获得图片原始文件名
String originalFilename = img.getOriginalFilename();
log.debug("头像名称:{}",originalFilename);
log.debug("头像大小:{}",img.getSize());
log.debug("上传路径:{}",realpath);

//1.处理头像的上传
String newFileName = uploadPhoto(img, originalFilename);
//2.保存员工信息
employee.setPhoto(newFileName);//保存头衔名字
employeeService.addEmp(employee);
return "redirect:/employee/lists";//保存成功跳转到员工列表
}

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
# 公共配置
server:
port: 8888
servlet:
context-path: /ems-thymeleaf

# Thymeleaf配置
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html

# 配置数据源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ems-thymeleaf?characterEncoding=UTF-8
username: root
password: 123456
# 暴露哪些资源可以通过项目名直接访问
web:
resources:
static-locations: classpath:/static/,file:${photo.file.dir}

# 配置mybatis
mybatis:
mapper-locations: classpath:com/study/mapper/*.xml
type-aliases-package: com.study.entity

# 配置日志
logging:
level:
root: info
com.study: debug

# 指定文件上传位置
photo:
file:
dir: D:\Software_Development\IDEA_code\SpringBoot\ems-thymeleaf\photos

完善MvcConfig

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
//通过在这里面配置,不需要再为每一个访问thymeleaf模板页面单独开发一个controller请求了
public void addViewControllers(ViewControllerRegistry registry) {
//ViewController: 请求路径,ViewName: 跳转视图
registry.addViewController("login").setViewName("login");
registry.addViewController("register").setViewName("regist");
registry.addViewController("addEmp").setViewName("addEmp");
}
}

addEmp.html

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
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymealf.org">
<head>
<title>添加员工信息</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css"
href="css/style.css" />
</head>

<body>
<div id="wrap">
<div id="top_content">
<div id="header">
<div id="rightheader">
<p>
2009/11/20
<br />
</p>
</div>
<div id="topheader">
<h1 id="title">
<a href="#">main</a>
</h1>
</div>
<div id="navigation">
</div>
</div>
<div id="content">
<p id="whereami">
</p>
<h1>
添加员工信息:
</h1>
<form th:action="@{/employee/addEmp}" method="post" enctype="multipart/form-data">
<table cellpadding="0" cellspacing="0" border="0"
class="form_table">
<tr>
<td valign="middle" align="right">
姓名:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="name" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
头像:
</td>
<td valign="middle" align="left">
<input type="file" width="" name="img" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
工资:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="salary" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
生日:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="birthday" />
</td>
</tr>
</table>
<p>
<input type="submit" class="button" value="确认添加" />
<input type="submit" class="button" value="返回列表" />
</p>
</form>
</div>
</div>
<div id="footer">
<div id="footer_bg">
ABC@126.com
</div>
</div>
</div>
</body>
</html>

访问路径:http://localhost:8888/ems-thymeleaf/employee/lists,添加员工信息

image-20240325130820871

image-20240325130837211

image-20240325130845073

25.10 更新员工信息

完善EmployeeController

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
/**
* @MethodName detail
* @Description 根据id查询员工详细信息
* @param: id
* @param: model
* @return: java.lang.String
* @Author chabai
* @Date 7:05 2022/6/29
*/
@RequestMapping("detail")
public String detail(Integer id,Model model){
log.debug("当前查询员工id:{}",id);
//根据id查询员工详细信息
Employee employee = employeeService.findById(id);
model.addAttribute("employee",employee);
return "updateEmp";//查询到的员工信息显示在更新页面
}

/**
* @MethodName updateEmp
* @Description 更新员工信息
* @param: employee
* @return: java.lang.String
* @Author chabai
* @Date 7:15 2022/6/29
*/
@RequestMapping("updateEmp")
public String updateEmp(Employee employee,MultipartFile img) throws IOException {
//1.判断是否更新头像 为空时说明头像未更新,
boolean notEmpty = !img.isEmpty();
log.debug("是否更新了头像:{}",notEmpty);
//不为空时说明头像更新了:需要删除老的头像,重新上传新的头像
if(notEmpty){
//1.根据id查询原始头像
String oldPhoto = employeeService.findById(employee.getId()).getPhoto();
//2.删除老的头像
File file = new File(realpath,oldPhoto);
if(file.exists())
file.delete();
//3.处理新的头像上传
String originalFilename = img.getOriginalFilename();
String newFileName = uploadPhoto(img, originalFilename);
//4.修改员工新的头像名称
employee.setPhoto(newFileName);
}
//为空时说明头像未更新,只需要更新基本信息即可
employeeService.updateEmp(employee);
//更新成功后跳转到员工列表页面
return "redirect:/employee/lists";
}

完善updateEmp.html

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
94
95
96
97
98
99
100
101
102
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>更新员工信息</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css"
th:href="@{/css/style.css}" />
</head>

<body>
<div id="wrap">
<div id="top_content">
<div id="header">
<div id="rightheader">
<p>
2009/11/20
<br />
</p>
</div>
<div id="topheader">
<h1 id="title">
<a href="#">main</a>
</h1>
</div>
<div id="navigation">
</div>
</div>
<div id="content">
<p id="whereami">
</p>
<h1>
更新员工信息:
</h1>
<form th:action="@{/employee/updateEmp}" method="post" enctype="multipart/form-data">
<table cellpadding="0" cellspacing="0" border="0"
class="form_table">
<tr>
<td valign="middle" align="right">
编号:
</td>
<td valign="middle" align="left">
<span th:text="${employee.id}"></span>
<input type="hidden" name="id" th:value="${employee.id}">
</td>
</tr>
<tr>
<td valign="middle" align="right">
姓名:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="name" th:value="${employee.name}"/>
</td>
</tr>
<tr>
<td valign="middle" align="right">
当前头像:
</td>
<td valign="middle" align="left">
<img th:src="@{/}+${employee.photo}" width="60">
<input type="hidden" th:value="${employee.photo}" name="photo">
</td>
</tr>
<tr>
<td valign="middle" align="right">
选择新头像:
</td>
<td valign="middle" align="left">
<input type="file" name="img" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
工资:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="salary" th:value="${employee.salary}"/>
</td>
</tr>
<tr>
<td valign="middle" align="right">
生日:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="birthday" th:value="${#dates.format(employee.birthday,'yyyy/MM/dd')}"/>
</td>
</tr>
</table>
<p>
<input type="submit" class="button" value="更新" />
<input type="button" class="button" value="返回列表" />
</p>
</form>
</div>
</div>
<div id="footer">
<div id="footer_bg">
ABC@126.com
</div>
</div>
</div>
</body>
</html>

完善EmployeeService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @MethodName findById
* @Description 根据id查询员工详细信息
* @param: id
* @return: com.study.entity.Employee
* @Author chabai
* @Date 7:03 2022/6/29
*/
Employee findById(Integer id);

/**
* @MethodName updateEmp
* @Description 更新员工信息
* @param: employee
* @Author chabai
* @Date 7:44 2022/6/29
*/
void updateEmp(Employee employee);

完善EmployeeServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @MethodName findById
* @Description 根据id查询员工信息
* @param: id
* @return: com.study.entity.Employee
* @Author chabai
* @Date 7:03 2022/6/29
*/
@Override
public Employee findById(Integer id) {
return employeeDAO.findById(id);
}

/**
* @MethodName updateEmp
* @Description 更新员工信息
* @param: employee
* @Author chabai
* @Date 7:45 2022/6/29
*/
@Override
public void updateEmp(Employee employee) {
employeeDAO.updateEmp(employee);
}

完善EmployeeDAO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @MethodName findById
* @Description 根据id查询员工详细信息
* @param: id
* @return: com.study.entity.Employee
* @Author chabai
* @Date 7:04 2022/6/29
*/
Employee findById(Integer id);

/**
* @MethodName updateEmp
* @Description 更新员工信息
* @param: employee
* @Author chabai
* @Date 7:47 2022/6/29
*/
void updateEmp(Employee employee);

完善EmployeeDAOMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--findById-->
<select id="findById" parameterType="Integer" resultType="Employee">
select
id,name,salary,birthday,photo
from employee
where id=#{id}
</select>

<!--updateEmp-->
<update id="updateEmp" parameterType="Employee">
update employee
set name=#{name},salary=#{salary},birthday=#{birthday},photo=#{photo}
where id=#{id}
</update>

测试路径:http://localhost:8888/ems-thymeleaf/employee/lists

(1)点击更新,进行员工信息更新

image-20240325131125044

(2)填写要修改的员工信息,修改完成后,点击更新

image-20240325131133793

(3)更新后的员工信息

image-20240325131141688

25.11 删除员工信息

完善EmpController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @MethodName deleteEmp
* @Description 删除员工信息
* @param: id
* @return: java.lang.String
* @Author chabai
* @Date 8:43 2022/6/29
*/
@RequestMapping("deleteEmp")
public String deleteEmp(Integer id){
log.debug("删除的员工id:{}",id);
//1.删除数据
String photo = employeeService.findById(id).getPhoto();
employeeService.deleteEmp(id);
//2.删除头像
File file = new File(realpath,photo);
if(file.exists())
file.delete();
return "redirect:/employee/lists";//删除成功后跳转到员工列表
}

完善emplist.html

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
94
95
96
97
98
99
100
101
102
103
104
105
106
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>emplist</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}" />
</head>
<body>
<div id="wrap">
<div id="top_content">
<div id="header">
<div id="rightheader">
<p>
2009/11/20
<br />
<a th:if="${session.user!=null}" th:href="@{/user/logout}">安全退出</a>
<a th:if="${session.user==null}" th:href="@{/login}">点我登录</a>
</p>
</div>
<div id="topheader">
<h1 id="title">
<a href="#">main</a>
</h1>
</div>
<div id="navigation">
</div>
</div>
<div id="content">
<p id="whereami">
</p>
<h1>
欢迎 <span th:if="${session.user!=null}" th:text="${session.user.username}"></span>
<span th:if="${session.user==null}" >游客</span>!
</h1>
<table class="table">
<tr class="table_header">
<td>
编号
</td>
<td>
姓名
</td>
<td>
头像
</td>
<td>
工资
</td>
<td>
生日
</td>
<td>
操作
</td>
</tr>
<tr th:each="employee,state:${employeeList}" th:class="${state.odd?'row1':'row2'}">
<td>
<span th:text="${employee.id}"></span>
</td>
<td>
<span th:text="${employee.name}"></span>
</td>
<td>
<img th:src="@{/}+${employee.photo}" width="60">
</td>
<td>
<span th:text="${employee.salary}"></span>
</td>
<td>
<span th:text="${#dates.format(employee.birthday,'yyyy/MM/dd')}"></span>
</td>
<td>
<a href="javascript:;" th:οnclick="'deleteEmployee('+${employee.id}+')'">删除</a>
<!-- <a th:href="@{/employee/detail?id=}+${employee.id}+'&name='+${employee.name}">更新</a>-->
<!-- <a th:href="@{/employee/detail(id=${employee.id},name=${employee.name})}">更新</a>-->
<a th:href="@{/employee/detail(id=${employee.id})}">更新</a>

</td>
</tr>
<script>
function deleteEmployee(id){
console.log(id);
if(window.confirm('确定要删除这条记录吗?')){
location.href='[[@{/employee/deleteEmp?id=}]]'+id;
}
}
</script>
</table>
<p>
<input type="button" class="button" value="添加" οnclick="addEmp()"/>
<script>
function addEmp(){
location.href = '[[@{/addEmp}]]';
}
</script>
</p>
</div>
</div>
<div id="footer">
<div id="footer_bg">
ABC@126.com
</div>
</div>
</div>
</body>
</html>

完善EmpService

1
2
3
4
5
6
7
8
/**
* @MethodName deleteEmp
* @Description 删除员工信息
* @param: id
* @Author chabai
* @Date 8:48 2022/6/29
*/
void deleteEmp(Integer id);

完善EmpServiceImpl

1
2
3
4
5
6
7
8
9
10
11
/**
* @MethodName deleteEmp
* @Description 删除员工信息
* @param: id
* @Author chabai
* @Date 8:49 2022/6/29
*/
@Override
public void deleteEmp(Integer id) {
employeeDAO.deleteEmp(id);
}

完善EmployeeDAO

1
2
3
4
5
6
7
8
/**
* @MethodName deleteEmp
* @Description 删除员工信息
* @param: id
* @Author chabai
* @Date 8:49 2022/6/29
*/
void deleteEmp(Integer id);

完善EmployeeDAOMapper

1
2
3
4
<!--deleteEmp-->
<delete id="deleteEmp" parameterType="Integer">
delete from employee where id=#{id}
</delete>

测试路径:http://localhost:8888/ems-thymeleaf/employee/lists,点击删除的同时,员工信息删除且photos中的头像图片也删除。

25.12 安全退出

完善UserController

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @MethodName logout
* @Description 安全退出
* @return: java.lang.String
* @Author chabai
* @Date 14:08 2022/6/29
*/
@RequestMapping("logout")
public String logout(HttpSession session){
session.invalidate();//session失效
return "redirect:/login";//跳转到登录页面
}

完善emplist.html

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
94
95
96
97
98
99
100
101
102
103
104
105
106
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>emplist</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}" />
</head>
<body>
<div id="wrap">
<div id="top_content">
<div id="header">
<div id="rightheader">
<p>
2009/11/20
<br />
<a th:if="${session.user!=null}" th:href="@{/user/logout}">安全退出</a>
<a th:if="${session.user==null}" th:href="@{/login}">点我登录</a>
</p>
</div>
<div id="topheader">
<h1 id="title">
<a href="#">main</a>
</h1>
</div>
<div id="navigation">
</div>
</div>
<div id="content">
<p id="whereami">
</p>
<h1>
欢迎 <span th:if="${session.user!=null}" th:text="${session.user.username}"></span>
<span th:if="${session.user==null}" >游客</span>!
</h1>
<table class="table">
<tr class="table_header">
<td>
编号
</td>
<td>
姓名
</td>
<td>
头像
</td>
<td>
工资
</td>
<td>
生日
</td>
<td>
操作
</td>
</tr>
<tr th:each="employee,state:${employeeList}" th:class="${state.odd?'row1':'row2'}">
<td>
<span th:text="${employee.id}"></span>
</td>
<td>
<span th:text="${employee.name}"></span>
</td>
<td>
<img th:src="@{/}+${employee.photo}" width="60">
</td>
<td>
<span th:text="${employee.salary}"></span>
</td>
<td>
<span th:text="${#dates.format(employee.birthday,'yyyy/MM/dd')}"></span>
</td>
<td>
<a href="javascript:;" th:οnclick="'deleteEmployee('+${employee.id}+')'">删除</a>
<!-- <a th:href="@{/employee/detail?id=}+${employee.id}+'&name='+${employee.name}">更新</a>-->
<!-- <a th:href="@{/employee/detail(id=${employee.id},name=${employee.name})}">更新</a>-->
<a th:href="@{/employee/detail(id=${employee.id})}">更新</a>

</td>
</tr>
<script>
function deleteEmployee(id){
console.log(id);
if(window.confirm('确定要删除这条记录吗?')){
location.href='[[@{/employee/deleteEmp?id=}]]'+id;
}
}
</script>
</table>
<p>
<input type="button" class="button" value="添加" οnclick="addEmp()"/>
<script>
function addEmp(){
location.href = '[[@{/addEmp}]]';
}
</script>
</p>
</div>
</div>
<div id="footer">
<div id="footer_bg">
ABC@126.com
</div>
</div>
</div>
</body>
</html>

测试路径:http://localhost:8888/ems-thymeleaf/employee/lists

(1)未登录

image-20240325131522203

(2)登录后

image-20240325131530433

(3)安全登出后,需要重新登录

image-20240325131541474

25.13 完整项目结构

image-20240325131558356

26.RestFul

26.1 引言

REST全称是**(Resources) Representational State Transfer**,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:”我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则(Rest不是标准 规范)。” 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。

REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。 所以我们这里描述的REST也是通过HTTP实现的REST。

传统web开发:基于javaweb开发,实现所有功能

有了Restful:软件设计风格|标准、简洁、层次、优雅

基于rest设计原则和约束的架构被称之为restFul。

总结:

  • Rest 词:没有更新的技术、组件、服务,但能让web请求能够利用web中的标准和能力更好的描述架构。
  • RestFul: 是一种以网络为基础构架的一种架构风格, 符合Rest设计原则和约束的架构被称为RestFul。
  • Restful 一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

26.2URL定义

资源:互联网所有的事物都可以被抽象为资源

  • 一首歌、一张图片、数据库一条记录等

资源操作:使用POST(添加)、DELETE(删除)、PUT(修改)、GET(查询),使用不同请求方法对资源进行操作。

  • 删除 delete

  • 查询 get

  • 添加 post

  • 修改 put (修改全部字段)| patch(更新部分字段)

26.3传统方式操作资源

http://127.0.0.1/item/queryUser.action?id=1 查询,GET

http://127.0.0.1/item/saveUser.action 新增,POST

http://127.0.0.1/item/updateUser.action 更新,PUT

http://127.0.0.1/item/deleteUser.action?id=1 删除,DELETE
注意:传统的操作是没有问题的,但大神认为是有问题的,有什么问题呢?你每次请求的接口或者地址,都在做描述,例如查询的时候用了queryUser,新增的时候用了saveUser,修改的时候用了updateUser,其实完全没有这个必要,我使用了get请求就是查询、使用post请求就是新增的请求、PUT就是修改、delete就是删除,我的意图很明显,完全没有必要做描述,这就是为什么有了restful。

26.4使用RESTful操作资源

【GET】 /users # 查询用户信息列表

【GET】 /users/1001 # 查看某个用户信息

【POST】 /users # 新建用户信息

【PUT】 /users/1001 # 更新用户信息(全部字段)

【PATCH】 /users/1001 # 更新用户信息(部分字段)

【DELETE】 /users/1001 # 删除用户信息

26.5Rest API设计风格原则

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
# 1.使用名词而不是动词
- 不要使用:
如:
/getAllUsers get /users 、 get /users/002
/createNewUser post /users
/deleteAllUser delete /users 、delete /users/001
/updateUser put|patch /users 、patch /users/001

# 2.Get方法和查询参数不应该涉及状态改变
- 使用PUT,POST和DELETE方法 而不是GET方法来改变状态,不要使用GET进行状态改变

# 3.使用复数名词
- 不要混淆名词单数和复数,为了保持简单,只对所有资源使用复数,如:
/cars 而不是 /car
/users 而不是 /user
/products 而不是 /product
/settings 而不是 /setting
/orders 而不是 /order

# 4. 使用子资源表达关系
- 如果一个资源与另外一个资源有关系,使用子资源,如:
GET /cars/711/drivers/ 返回 car 711的所有司机
GET /cars/711/drivers/4 返回 car 711的4号司机
GET /users/11/pets 返回 user 11的所有宠物
GET /users/11/pets/2 返回 user 11的2号宠物

# 5.使用Http头声明序列化格式
- http有请求有响应,发请求到控制器是请求,响应回来会有一个响应
- 在客户端和服务端双方都要知道通讯的格式,格式在HTTP-Header中指定,如:
Content-Type:定义请求格式
Accept:定义系列可接受的响应格式

# 6.为集合提供过滤排序选择和分页等功能
- Filtering过滤:使用唯一的查询参数进行
GET /cars?color=red 返回红色的cars
GET /cars?seats<=2 返回小于两座位的cars集合

- Sorting排序:允许针对多个字段排序
GET /cars?sort=-manufactorer,+model
这是返回根据生产者降序和模型升序排列的car集合

- Field selection
移动端能够显示其中一些字段,它们其实不需要一个资源的所有字段,给API消费者一个选择字段的能力,这会降低网络流量,提高API可用性。
GET /cars?fields=manufacturer,model,id,color

- Paging分页
使用limit和offset.实现分页,缺省时,limit=20和offset=0;
GET /cars?offset=10&limit=5
为了将总数发给客户端,使用订制的HTTP头: X-Total-Count.
链接到下一页或上一页可以在HTTP头的link规定,遵循Link规定:
Link: <https://blog.mwaysolutions.com/sample/api/v1/cars?offset=15&limit=5>;
rel="next",<https://blog.mwaysolutions.com/sample/api/v1/cars?offset=50&limit=3>;
rel="last",<https://blog.mwaysolutions.com/sample/api/v1/cars?offset=0&limit=5>;
rel="first",<https://blog.mwaysolutions.com/sample/api/v1/cars?offset=5&limit=5>;
rel="prev",

# 7.版本化你的API 支付宝 v1 v2 v3
- 使得API版本变得强制性,不要发布无版本的API,使用简单数字,避免小数点,如:2.5.
一般在Url后面使用?v,如:/blog/api/v1

# 8. 使用Http状态码处理错误
- 如果你的API没有错误处理是很难的,只是返回500和出错堆栈不一定有用
- Http状态码提供70个出错,我们只要使用10个左右:
`200 – OK – 一切正常
`201 – OK – 新的资源已经成功创建
`204 – OK – 资源已经成功删除
`304 – Not Modified – 客户端使用缓存数据
`400 – Bad Request – 请求无效,需要附加细节解释如 "JSON无效"
`401 – Unauthorized – 请求需要用户验证
`403 – Forbidden – 服务器已经理解了请求,但是拒绝服务或这种请求的访问是不允许的。
`404 – Not found – 没有发现该资源
`422 – Unprocessable Entity – 只有服务器不能处理实体时使用,比如图像不能被格式化,或者重要字段丢失。
`500 – Internal Server Error – API开发者应该避免这种错误。
使用详细的错误包装错误:状态码、数据、header头信息
{
"errors": [
{
"userMessage": "Sorry, the requested resource does not exist",
"internalMessage": "No car found in the database",
"code": 34,
"more info": "http://dev.mwaysolutions.com/blog/api/v1/errors/12345"
}
]
}

26.6Rest API案例

注:可先过一边传统的开发

新建Spring Initializr、引入Spring Web的项目spring-boot-day8

创建实体类User

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
package com.study.entity;

import java.util.Date;

/**
* @ClassName User
* @Description TODO
* @Author chabai
* @Date 2022/6/29 16:13
* @Version 1.0
*/
public class User {
private Integer id;
private String name;
private Double salary;
private Date birthday;

public User() {
}

public User(Integer id, String name, Double salary, Date birthday) {
this.id = id;
this.name = name;
this.salary = salary;
this.birthday = birthday;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Double getSalary() {
return salary;
}

public void setSalary(Double salary) {
this.salary = salary;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", salary=" + salary +
", birthday=" + birthday +
'}';
}
}

创建实体类Pet

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.study.entity;

/**
* @ClassName Pet
* @Description TODO
* @Author chabai
* @Date 2022/6/29 17:48
* @Version 1.0
*/
public class Pet {
private Integer id;
private String name;
private Integer age;

public Pet() {
}

public Pet(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "Pet{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}

创建控制器UserController

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
package com.study.controller.v1;

import com.study.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import java.util.Date;

/**
* @ClassName UserController
* @Description TODO
* @Author chabai
* @Date 2022/6/29 16:16
* @Version 1.0
*/
@RestController //专用于restful风格的注解=@Controller + @ResponseBody
//@Controller:专用于传统开发的注解
@RequestMapping("/v1/users") //符合使用名词复数形式
public class UserController {

//新建日志对象
private static final Logger logger = LoggerFactory.getLogger(UserController.class);

/**
* @MethodName findUserById
* @Description 根据id查询某个用户的详细信息
* @param: id
* @return: com.study.entity.User
* @Author chabai
* @Date 16:28 2022/6/29
*/
//@RequestMapping(value = "/{id}",method = RequestMethod.GET) 此种方式太繁琐,推荐使用下面这种
//传统web开发:不是restful风格 就是用@RequestMapping映射请求路径:支持任何请求请求这个路径 GET POST DELETE PUT
@GetMapping("/{id}") //@RequestMapping的子类注解,使用GetMapping时只能使用GET方式访问当前请求
//@ResponseBody //将控制器方法返回值转换为json格式
//@PathVariable:在路径中获取请求参数
public User findUserById(@PathVariable("id") Integer id){
logger.info("本次id:{}",id);
return new User(id,"茶白",1234.56,new Date());
}
}

测试路径:http://localhost:8080/v1/users/1

image-20240325132532612

安装Postman进行测试:Download Postman | Get Started for Free (访问官网后自动识别版本,下载后双击打开即安装完毕)

用来测试restful风格的接口

image-20240325132556389

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package com.study.controller.v1;

import com.study.entity.Pet;
import com.study.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
* @ClassName UserController
* @Description TODO
* @Author chabai
* @Date 2022/6/29 16:16
* @Version 1.0
*/
@RestController //专用于restful风格的注解=@Controller + @ResponseBody
//@Controller:专用于传统开发的注解
@RequestMapping("/v1/users") //符合使用名词复数形式
public class UserController {
//新建日志对象
private static final Logger logger = LoggerFactory.getLogger(UserController.class);

/**
* @MethodName findUserById
* @Description 根据id查询某个用户的详细信息
* @param: id
* @return: com.study.entity.User
* @Author chabai
* @Date 16:28 2022/6/29
*/
//@RequestMapping(value = "/{id}",method = RequestMethod.GET) 此种方式太繁琐,推荐使用下面这种
@GetMapping("/{id}") //@RequestMapping的子类注解,使用GetMapping时只能使用GET方式访问当前请求
//@ResponseBody //将控制器方法返回值转换为json格式
//@PathVariable:在路径中获取请求参数
public User findUserById(@PathVariable("id") Integer id){
logger.info("本次id:{}",id);
return new User(id,"茶白",1234.56,new Date());
}

/**
* @MethodName findAllUser
* @Description 查询所有用户信息
* @return: java.util.List<com.study.entity.User>
* @Author chabai
* @Date 17:24 2022/6/29
*/
@GetMapping
//@ResponseBody
public List<User> findAllUser(){
ArrayList<User> users = new ArrayList<>();
users.add(new User(1,"张三",1000.00,new Date()));
users.add(new User(2,"李四",2000.00,new Date()));
users.add(new User(3,"王五",3000.00,new Date()));
return users;
}

/**
* @MethodName addUser
* @Description 新增用户信息
* @param: user
* @Author chabai
* @Date 17:28 2022/6/29
*/
@PostMapping
//@ResponseBody //将方法的返回值转化为json格式,并响应请求 这里有响应但响应里没有任何数据
//@RequestBody: 接收请求的json格式数据,将json格式数据转化为对象
public void addUser(@RequestBody User user){
logger.info("name:{},salary:{},birthday:{}",user.getName(),user.getSalary(),user.getBirthday());
//调用业务方法

}

/**
* @MethodName updateUser
* @Description 更新用户信息
* @param: user
* @Author chabai
* @Date 17:30 2022/6/29
*/
@PutMapping("/{id}")
//@ResponseBody
public void updateUser(@PathVariable("id") Integer id,@RequestBody User user){
logger.info("id:{},name:{},salary:{},birthday:{}",id,user.getName(),user.getSalary(),user.getBirthday());
//调用业务方法

}

/**
* @MethodName deleteUser
* @Description 删除用户信息
* @param: id
* @Author chabai
* @Date 17:32 2022/6/29
*/
@DeleteMapping("/{id}")
@ResponseBody
public void deleteUser(@PathVariable("id") Integer id){
logger.info("本次删除用户的id:{}",id);
}
//注:我们的浏览器只支持get和post restfulapi主要的通信方式是Ajax ajax是支持restful所有请求方式的 系统间通信也是支持的
//我们浏览器就向我们暴漏了get和post rest我们也不希望浏览器可以直接进行调用 除了浏览器可以直接操作的查询 剩下的不希望直接能处理 可以通过ajax或者系统间调用的http协议客户端测试

/**
* @MethodName findPetById
* @Description 根据宠物id查询主人的一个宠物 资源和资源之间有关系
* @param: id
* @param: petId
* @return: com.study.entity.Pet
* @Author chabai
* @Date 17:54 2022/6/29
*/
@GetMapping("/{id}/pets/{petId}")
public Pet findPetById(@PathVariable("id") Integer id,@PathVariable("petId") Integer petId){
logger.info("宠物主人的id:{}",id);
logger.info("宠物id:{}",petId);
return new Pet(1,"小灰灰",5);
}

/**
* @MethodName findAllPet
* @Description 查询主人的所有宠物
* @param: id
* @return: java.util.List<com.study.entity.Pet>
* @Author chabai
* @Date 17:57 2022/6/29
*/
@GetMapping("/{id}/pets")
public List<Pet> findAllPet(@PathVariable("id") Integer id){
logger.info("宠物主人的id:{}",id);
List<Pet> pets = Arrays.asList(
new Pet(1,"熊大",20),
new Pet(2,"熊二",10),
new Pet(3,"光头强",50)
);
return pets;
}

}

测试路径:

(1)http://localhost:8080/v1/users

image-20240325132659039

(2)http://localhost:8080/v1/users

image-20240325141326676

(3)http://localhost:8080/v1/users/1

image-20240325141342191

(4)http://localhost:8080/v1/users/1

image-20240325141352999

(5)http://localhost:8080/v1/users/1/pets/1

image-20240325141406544

(6)http://localhost:8080/v1/users/1/pets

image-20240325141416937

RestFul标准版:利用RestFul响应类ResponseEntity和状态码HttpStatus优化UserController

  • ResponseEntity:SpringMVC封装的一个专用于RestFul的响应类,这个类在响应时可以提供响应的状态码,同时还可以自定义响应头信息。

  • HttpStatus:SpringMVC封装的一个枚举类型,这个类中都是网络中状态码

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package com.study.controller.v1;

import com.study.entity.Pet;
import com.study.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
* @ClassName UserController
* @Description TODO
* @Author chabai
* @Date 2022/6/29 16:16
* @Version 1.0
*/
@RestController //专用于restful风格的注解=@Controller + @ResponseBody
//@Controller:专用于传统开发的注解
@RequestMapping("/v1/users") //符合使用名词复数形式
public class UserController {
//新建日志对象
private static final Logger logger = LoggerFactory.getLogger(UserController.class);

/**
* 补充:
* 1.ResponseEntity:SpringMVC封装的一个专用于RestFul的响应类,这个类在响应时可以提供响应的状态码,
* 同时还可以自定义响应头信息。
* 2.HttpStatus:SpringMVC封装的一个枚举类型,这个类中都是网络中状态码
*/

/**
* @MethodName findUserById
* @Description 根据id查询某个用户的详细信息
* @param: id
* @return: com.study.entity.User
* @Author chabai
* @Date 16:28 2022/6/29
*/
//@RequestMapping(value = "/{id}",method = RequestMethod.GET) 此种方式太繁琐,推荐使用下面这种
@GetMapping("/{id}") //@RequestMapping的子类注解,使用GetMapping时只能使用GET方式访问当前请求
//@ResponseBody //将控制器方法返回值转换为json格式
//@PathVariable:在路径中获取请求参数
public ResponseEntity<User> findUserById(@PathVariable("id") Integer id){
logger.info("本次id:{}",id);
User user = new User(id, "茶白", 1234.56, new Date());
return new ResponseEntity<>(user, HttpStatus.OK);
}

/**
* @MethodName findAllUser
* @Description 查询所有用户信息
* @return: java.util.List<com.study.entity.User>
* @Author chabai
* @Date 17:24 2022/6/29
*/
@GetMapping
//@ResponseBody
public ResponseEntity<List<User>> findAllUser(){
ArrayList<User> users = new ArrayList<>();
users.add(new User(1,"张三",1000.00,new Date()));
users.add(new User(2,"李四",2000.00,new Date()));
users.add(new User(3,"王五",3000.00,new Date()));
return new ResponseEntity<>(users,HttpStatus.OK);
}

/**
* @MethodName addUser
* @Description 新增用户信息
* @param: user
* @Author chabai
* @Date 17:28 2022/6/29
*/
@PostMapping
//@ResponseBody //将方法的返回值转化为json格式,并响应请求
//@RequestBody: 接收请求的json格式数据,将json格式数据转化为对象
public ResponseEntity<Void> addUser(@RequestBody User user){
logger.info("name:{},salary:{},birthday:{}",user.getName(),user.getSalary(),user.getBirthday());
//调用业务方法
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

/**
* @MethodName updateUser
* @Description 更新用户信息
* @param: user
* @Author chabai
* @Date 17:30 2022/6/29
*/
@PutMapping("/{id}")
//@ResponseBody
public ResponseEntity<Void> updateUser(@PathVariable("id") Integer id,@RequestBody User user){
logger.info("id:{},name:{},salary:{},birthday:{}",id,user.getName(),user.getSalary(),user.getBirthday());
//调用业务方法
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

/**
* @MethodName deleteUser
* @Description 删除用户信息
* @param: id
* @Author chabai
* @Date 17:32 2022/6/29
*/
@DeleteMapping("/{id}")
@ResponseBody
public void deleteUser(@PathVariable("id") Integer id){
logger.info("本次删除用户的id:{}",id);
}

/**
* @MethodName findPetById
* @Description 根据宠物id查询主人的一个宠物
* @param: id
* @param: petId
* @return: com.study.entity.Pet
* @Author chabai
* @Date 17:54 2022/6/29
*/
@GetMapping("/{id}/pets/{petId}")
public ResponseEntity<Pet> findPetById(@PathVariable("id") Integer id,@PathVariable("petId") Integer petId){
logger.info("宠物主人的id:{}",id);
logger.info("宠物id:{}",petId);
Pet pet = new Pet(1, "小灰灰", 5);
return new ResponseEntity<>(pet,HttpStatus.OK);
}

/**
* @MethodName findAllPet
* @Description 查询主人的所有宠物
* @param: id
* @return: java.util.List<com.study.entity.Pet>
* @Author chabai
* @Date 17:57 2022/6/29
*/
@GetMapping("/{id}/pets")
public ResponseEntity<List<Pet>> findAllPet(@PathVariable("id") Integer id){
logger.info("宠物主人的id:{}",id);
List<Pet> pets = Arrays.asList(
new Pet(1,"熊大",20),
new Pet(2,"熊二",10),
new Pet(3,"光头强",50)
);
return new ResponseEntity<>(pets,HttpStatus.OK);
}
}

再次启动服务,用Postman测试上面路径,从Postman中可以查看到相应状态码

image-20240325141617542

26.7最终项目结构

image-20240325141634263

26开发方式演变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 传统方式开发:基于javaweb开发
- 项目业务代码 + view 写在一个项目中
- 如业务代码+view(isp、thymeleaf、freemark)在一起的
- 概述:在前后端不分离的架构中,前端和后端的职责通常不会明确地分离,后端负责生成并渲染整个HTML页面,包括页面渲染,数据处理和业务逻辑,提供完整的页面。后端承担了生成整个页面的责任,而前端的工作相对较少,主要是一些简单的交互和样式调整以及美化。这种架构模式适用于一些简单的 Web 应用程序,但在复杂的应用场景下可能会导致前端和后端的耦合度高、开发效率低下等问题。
- 页面渲染:后端负责生成整个页面的 HTML 结构,通常使用模板引擎(如JSP、Thymeleaf、Freemarker等)将动态数据嵌入到页面模板中,最终生成完整的 HTML 页面。
- 数据处理和业务逻辑:后端负责处理客户端的请求,并执行相应的业务逻辑。这包括从数据库中检索数据、对数据进行逻辑处理、验证用户身份等操作。
- 提供完整的页面:后端生成的页面通常是一个完整的 HTML 文档,其中包含了所有的页面内容、样式和交互行为。后端负责将生成的页面直接返回给客户端浏览器。
- 视图解析器:在传统的开发中,特别是在使用基于服务器端渲染的 Web 应用程序中,视图解析器起着至关重要的作用,通常使用模板引擎来构建动态页面。视图解析器会根据请求中的 URL 匹配相应的模板,并将模板渲染成最终的 HTML 页面,然后返回给客户端。
- 注意:在以前没有用到restful风格时,我们请求一个后端接口,正常返回走的是视图解析器,除非我们引入依赖使用注解使它以json数据形式返回这样就不走视图解析器了

# 过渡引言
- 传统的 Web 开发确实通常由一个后端程序员完成,因为在那个时代,Web 应用的需求相对简单,前后端的职责交织在一起,而且前端技术相对简单,主要是 HTML、CSS 和少量的 JavaScript。然而,随着互联网的发展和用户需求的提高,Web 应用变得越来越复杂,传统的后端渲染模式已经不能满足这些需求,因此引入了前后端分离的开发模式。

# 前后分段分离
- 前端系统(vue)
- 前后端通信方式:axja异步通信 通过异步请求调用我们后端的restfulapi去完成整个数据的交互
- 后端系统 (java) 只用写接口即可 即controller 用restfuapi风格
- 概述:前后端分离架构下,前端和后端的职责明确分离。前端负责 UI 展示和用户交互,后端负责数据处理和业务逻辑。因此,后端不再需要渲染动态页面,而是提供数据接口供前端调用。后端对各种业务逻辑进行处理,把处理的结果得到的数据封装成接口以json或者xml形式响应给调用者,并写接口文档,供前端调用,前端根据接口文档调接口,拿到json数据并进行相应处理,最后展示到页面上。
- 接口测试工具postman 接口文档生成工具swapper

前后端不分离时代

image-20240423175620492

单页面下的全分离时代

image-20240423175508798

28.异常处理

27.0概述

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
1.开发方式演变
a.传统方式开发 基于javaweb方式开发
项目业务代码 + view 写在一个项目中
业务代码+view(jsp、thymeleaf、freemark)
b.前后端分离开发 前后端分离 天下
前端系统(Vue)
ajax 异步请求
后端系统(java) 接口 controller ===> 传统web方式 /user/findById?id=21 =====> GET POST PUT DELETE GET /users users/001(RestFul API)

2.springboot异常处理
异常处理:
当出现异常改如何处理====>当controller中方法在执行过程中如果出现异常,我们应该如何处理异常这种方式 称之为异常处理
a.传统方式开发异常处理 以整合thymeleaf为例(jsp也可)
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {
//resolveExcpetion: 当控制器中任意一个方法出现异常时,如果该控制器的方法没有自己异常处理(try...catch),则会进入当前方法
//注意:在异常处理这个方法中 完成自定义异常处理
//参数1: request 当前请求对象
//参数2: response 当前请求对应响应对象
//参数3: 当前出现错误的方法对象
//参数4: 出现异常的异常对象
//返回值: modelAndview 模型和视图
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("这是全局异常处理....");
System.out.println("当前异常为: "+ex);
ModelAndView modelAndView = new ModelAndView();

//针对不同异常类型跳转不同页面
if(ex instanceof UserNameNotFoundException){
modelAndView.setViewName("error");
return modelAndView;
}
modelAndView.setViewName("500");
return modelAndView;
}
}

b.前后端分离开发异常处理 前端系统通过ajax和后端系统交互,即使后端系统出错了也不可能返回一张页面,要返回一个json格式错误
不能返回moduleandview
ajax<------>controller

@ControllerAdvice
public class GlobalExceptionResolver {
//处理指定异常
@ExceptionHandler(value = IllegalNumberException.class)
@ResponseBody
public ResponseEntity<String> illegalNumberExceptionHandler(Exception ex) {
System.out.println("进入非法参数异常处理");
return new ResponseEntity<String>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}

//处理exception子类异常
@ExceptionHandler(value = Exception.class) //用在方法上 作用:用来处理指定异常 value属性: 用来指定处理异常类型
@ResponseBody
public ResponseEntity<String> exceptionHandler(Exception ex) {
System.out.println("进入自定义异常处理");
return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}

27.1传统开发的异常处理 以整合thymeleaf为例

新建Spring Initializr、Spring Web项目

pom.xml引入thymeleaf依赖

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

修改application.properties为application.yml,添加thymeleaf配置

1
2
3
4
5
6
# thymeleaf配置
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
cache: flase#记得idea也要做相应设置

新建hello.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello</title>
</head>
<body>
<h1>Hello, SpringBoot!</h1>
</body>
</html>

新建MvcConfig配置类 由于我们不能直接访问页面 又没有写控制器 只能通过配置类访问页面 查看效果

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

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @ClassName MvcConfig
* @Description TODO
* @Author chabai
* @Date 2022/7/2 18:28
* @Version 1.0
*/
@Configuration //一定要添加此配置
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//配置请求和页面视图映射,设置后可不经过页面直接访问html页面
registry.addViewController("/hello")
.setViewName("hello");
}
}

访问127.0.0.1:8080/hello

1
2
Hello, SpringBoot!
# ==>thymeleaf正常运行

新建DemoController

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
package com.study.controller;

import com.study.exceptions.UserNameNotFoundException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @ClassName DemoController
* @Description TODO
* @Author chabai
* @Date 2022/7/2 18:31
* @Version 1.0
*/
@Controller
@RequestMapping("demo")
public class DemoController {
@RequestMapping("demo")
public String demo(){
System.out.println("demo ok...");
int n = 1 / 0; //除零异常
return "hello";
}

@RequestMapping("login")
public String login(String username, String password){
System.out.println("login...");
System.out.println("username = " + username);
System.out.println("password = " + password);
if("xiaocui".equals(username) && "123456".equals(password)){
return "hello";
}else{
throw new UserNameNotFoundException("用户名不正确!");
}
}

}

新建500.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>500</title>
</head>
<body>
<h1>系统内部错误,请稍后再试...</h1>
</body>
</html>

新建error.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>error</title>
</head>
<body>
<h1>系统错误,用户名不正确!!!</h1>
</body>
</html>

自定义全局异常解析类GlobalExceptionResolver 新建exception包存放异常处理类

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
package com.study.exceptions;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

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

/**
* @ClassName GlobalExceptionResolver
* @Description 全局异常解析类
* @Author chabai
* @Date 2022/7/2 18:59
* @Version 1.0
*/
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {
/**
* @MethodName resolveException
* @Description 当控制器中任意一个方法出现异常时,如果该控制器的方法没有自己的异常处理(try-catch),
* 则会进入当前方法
//在异常处理这个方法中,完成自定义异常处理
* @param: request 当前请求对象
* @param: response 当前响应对象
* @param: handler 当前出现错误的方法对象
* @param: ex 出现异常的异常对象
* @return: org.springframework.web.servlet.ModelAndView 模型和视图 出现错误要跳转哪些页面(view)跳转视图要不要出一些数据(module)
* @Author chabai
* @Date 19:00 2022/7/2
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("这是全局异常处理...");
System.out.println("当前异常为:" + ex);
ModelAndView modelAndView = new ModelAndView();

//针对不同异常类型跳转不同页面
if(ex instanceof UserNameNotFoundException){
modelAndView.setViewName("error");
return modelAndView;
}
modelAndView.setViewName("500");
return modelAndView;
}
}

自定义异常UserNameNotFoundException

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

/**
* @ClassName UserNameNotFoundException
* @Description 自定义异常:用户名不存在
* @Author chabai
* @Date 2022/7/2 18:54
* @Version 1.0
*/
public class UserNameNotFoundException extends RuntimeException{
public UserNameNotFoundException(String message) {
super(message);
}
}

测试路径:

(1)http://localhost:8080/demo/demo

1
2
3
demo ok...
这是全局异常处理...
当前异常为:java.lang.ArithmeticException: / by zero

(2)http://localhost:8080/demo/login?username=xiaozhu&password=123456

1
2
3
4
5
login...
username = xiaozhu
password = 123456
这是全局异常处理...
当前异常为:com.study.exceptions.UserNameNotFoundException: 用户名不正确!

image-20240325142048073项目最终结构:

27.2RestFul的异常处理 前后端分离

新建Spring Initializr、Spring Web项目spring-boot-day10、启动下看环境是否正常运行

新建控制器DemoController

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
package com.study.controller;

import com.study.exceptions.IllegalNumberException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @ClassName DemoController
* @Description TODO
* @Author chabai
* @Date 2022/7/2 22:06
* @Version 1.0
*/
@RestController
@RequestMapping("demos")
public class DemoController {
/**
* @MethodName demos
* @Description 测试查询所有
* @return: org.springframework.http.ResponseEntity<java.lang.String>
* @Author chabai
* @Date 22:08 2022/7/2
*/
@GetMapping//测试路径:http://localhost:8080/demos
public ResponseEntity<String> demos(){
System.out.println("demo ok");
return new ResponseEntity<>("demo ok", HttpStatus.OK);
}

/**
* @MethodName demo
* @Description 测试查询一个
* @param: id
* @return: org.springframework.http.ResponseEntity<java.lang.String>
* @Author chabai
* @Date 22:10 2022/7/2
*/
@GetMapping("/{id}") //测试路径:http://localhost:8080/demos/1
public ResponseEntity<String> demo(@PathVariable("id") Integer id){
System.out.println("demo ok" + id);
if(id<0)
throw new IllegalNumberException("无效id,请检查!");
return new ResponseEntity<>("demo ok",HttpStatus.OK);
}
}

新建全局异常解析类GlobalExceptionResolver

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
package com.study.exceptions;

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;

/**
* @ClassName GlobalExceptionResolver
* @Description TODO
* @Author chabai
* @Date 2022/7/2 22:11
* @Version 1.0
*/
@ControllerAdvice
public class GlobalExceptionResolver {
//处理指定异常
@ExceptionHandler(value = IllegalNumberException.class) 用在方法上 作用:用来处理指定异常 value属性:用来指定处理异常类型
@ResponseBody
public ResponseEntity<String> IllegalNumberException(Exception ex){
System.out.println("进入非法参数异常处理");
return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}

//处理Exception子类异常
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseEntity<String> exceptionHandler(Exception ex){
System.out.println("进入自定义异常处理");
return new ResponseEntity<>(ex.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR);
}
}

自定义异常IllegalNumberException

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

/**
* @ClassName IllegalNumberException
* @Description 自定义异常
* @Author chabai
* @Date 2022/7/2 22:17
* @Version 1.0
*/
public class IllegalNumberException extends RuntimeException{
public IllegalNumberException(String message) {
super(message);
}
}

测试路径:

(1)http://localhost:8080/demos

demo ok

image-20240325142334464

(2)http://localhost:8080/demos/1

demo ok1

image-20240325142356752

(3)http://localhost:8080/demos/0

demo ok0

image-20240325142410503

(4)http://localhost:8080/demos/-1

demo ok-1
进入非法参数异常处理

image-20240325142424399

项目结构

image-20240325142441041

29.CORS跨域

28.0概述

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
1.什么是CORS
定义: 跨域资源共享

2.什么跨域资源共享
解释: 允许浏览器可以从当前源服务器通过ajax访问另外一个源服务地址
前端系统 域名 ajax 后端系统 域名

超级链接 form表单提交不存在跨域资源共享问题


3.同源策略
是浏览器的一个默认安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。 同源策略是浏览器安全的基石 限ajax请求

4.什么是源 origin
源[origin]就是协议、域名和端口号。例如:http://www.baidu.com:80这个URL。
协议: http
域名: www.baidu.com
端口: 80

url: http://www.baidu.com:80
url: https://www.baidu.com:80
url: http://www.baizhiedu.com:80
url: http://www.baidu.com:90 都不同源协议 同源:域名 端口号不能有差异

测试:
http://www.a.com/test/index.html 端口没指定默认80端口
1.http://www.a.com/dir/page.html 同源
2.http://www.child.a.com/test/index.html 不同源
3.https://www.a.com/test/index.html 不同源
4.http://www.a.com:8080/test/index.html 不同源

5.哪些操作不会受到同源限制
1.<script src="..."></script>,
<img>,
<link>,
<iframe>

6.哪些操作会受到同源限制
ajax
报错出现跨域: Access-Control-Allow-Origin

7.模拟跨域错误

8.springboot中如何解决跨域问题?
1.局部解决跨域
@CrossOrigin: 这个注解用在类上 代表解决类中所有方法运行允许其他域中资源访问
2.全局解决跨域
@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);
}
}

28.1CORS简介

CORS:CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest(ajax)请求,从而克服了AJAX只能同源使用的限制。

**同源策略[same origin policy]**:是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。同源策略是浏览器安全的基石。

源[origin]:就是协议、域名和端口号。例如:百度一下,你就知道这个URL中:协议为http、域名为www.baidu.com、端口号为80。

同源:若地址里面的协议、域名和端口号均相同,则属于同源。

1
2
3
4
5
6
# 同源举例
- 例如判断下面的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**

image-20240325142752524

模拟跨域错误:

pom.xml引入thymeleaf依赖

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

修改application.properties为application.yml,添加thymeleaf配置

1
2
3
4
5
6
# thymeleaf配置
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
cache: flase#记得idea也要做相应设置

新建cors.html 在restful写thymeleaf页面 不正确只是作为演示 !!!

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>
<script>
function test(){
//1.创建xhr对象
var xhr = new XMLHttpRequest();

//1.5 处理结果
xhr.onreadystatechange = function(){
if(xhr.status==200 && xhr.readyState==4){
console.log(xhr.responseText);
document.getElementById("msg").innerText="返回结果为: "+xhr.responseText;

}
}

//2.发送请求
xhr.open("GET","http://localhost:8080/demos");
xhr.send();
}
</script>
</head>
<body>
<h1>cors跨域测试</h1>
<input type="button" value="点我发送ajax请求" οnclick="test()">
<h4 id="msg"></h4>
</body>
</html>

新建MvcConfig

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

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @ClassName MvcConfig
* @Description 配置后可不经过控制器直接访问html页面!!!
* @Author chabai
* @Date 2022/7/3 10:08
* @Version 1.0
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/cors").setViewName("cors");
}
}

启动项目后,浏览器访问:http://localhost:8080/cors,可以正常访问,不存在跨域问题。

注:在跨域中localhost不能解析为127.0.0.1 不能认为相同

image-20240325143015377

使用IDEA内置的浏览器打开页面:此时路径变成了http://localhost:63342/spring-boot-day10/templates/cors.html?_ijt=oru29oe9dkg5rkhii47uic36ig&_ij_reload=RELOAD_ON_SAVE,存在跨域问题。 端口不一样了

image-20240325143042207

28.2Springboot中如何解决cors跨域问题 前后端不在一个域 如何让前端系统访问后端接口 ===》cors跨域资源共享 需要做些配置后可允许浏览器从当前域访问另一个域 默认是不可以的

以下两种方法使用其中一种即可:

(1)控制器类上添加@CrossOrigin注解 局部解决跨域

这个注解用在类上 代表解决类中所有方法运行允许其他域中资源访问

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
package com.study.controller;

import com.study.exceptions.IllegalNumberException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
* @ClassName DemoController
* @Description TODO
* @Author chabai
* @Date 2022/7/2 22:06
* @Version 1.0
*/
@CrossOrigin //允许这个Controller中所有请求被其他域访问
@RestController
@RequestMapping("demos")
public class DemoController {
/**
* @MethodName demos
* @Description 测试查询所有
* @return: org.springframework.http.ResponseEntity<java.lang.String>
* @Author chabai
* @Date 22:08 2022/7/2
*/
@GetMapping//测试路径:http://localhost:8080/demos
public ResponseEntity<String> demos(){
System.out.println("demo ok");
return new ResponseEntity<>("demo ok", HttpStatus.OK);
}

/**
* @MethodName demo
* @Description 测试查询一个
* @param: id
* @return: org.springframework.http.ResponseEntity<java.lang.String>
* @Author chabai
* @Date 22:10 2022/7/2
*/
@GetMapping("/{id}") //测试路径:http://localhost:8080/demos/1
public ResponseEntity<String> demo(@PathVariable("id") Integer id){
System.out.println("demo ok" + id);
if(id<0)
throw new IllegalNumberException("无效id,请检查!");
return new ResponseEntity<>("demo ok",HttpStatus.OK);
}
}

(2)全局解决跨域问题:编写cors全局配置类,对所有页面生效 放到配置包下

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
package com.study.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
* @ClassName CorsConfig
* @Description 全局配置解决跨域问题
* @Author chabai
* @Date 2022/7/3 10:35
* @Version 1.0
*/
@Configuration
public class CorsConfig {
@Bean //注册一个专门解决跨域的filter
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);
}
}

最终项目结构:

image-20240325143235517

30.Jasypt加密

29.0 概述

1
2
3
4
5
1.原理
引入jasypt依赖
2.使用
(1)类 =====> root + 秘钥(暴露) =====> 加密之后结果 每一次运行都会生成一个新的加密结果 每个加密结果都是可用
(2)加密之后结果 ====> 类.方法 秘钥 =====> root

29.1 引言

Jasypt 也即Java Simplified Encryption是Sourceforge.net上的一个开源项目。在当地时间11月23号的通告中,Jasypt 1.4的新特征包括:加密属性文件(encryptable properties files)、Spring Framework集成、加密Hibernate数据源配置、新的命令行工具、URL加密的Apache wicket集成以及升级文档。

根据Jasypt文档,该技术可用于加密任务与应用程序,例如加密密码、敏感信息和数据通信、创建完整检查数据的sums. 其他性能包括高安全性、基于标准的加密技术、可同时单向和双向加密的加密密码、文本、数字和二进制文件。Jasypt也可以与Acegi Security整合也即Spring Security。Jasypt亦拥有加密应用配置的集成功能,而且提供一个开放的API从而任何一个Java Cryptography Extension都可以使用Jasypt。

Jasypt还符合RSA标准的基于密码的加密,并提供了无配置加密工具以及新的、高可配置标准的加密工具。

29.2SpringBoot整合Jasypt加密 我们这里以SpringBoot整合Jasypt加密为例 普通java也可以

新建Spring Initializr、Spring Web的spring-boot-jasypt项目

pom.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
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>

<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>

<!--mybatis-spring-boot-starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>

<!--jasypt加密-->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>

修改application.properties为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
# 对主机名进行加密
mysql:
host: ENC(+Szc93CJDBbbOZShvgnUxeZiec3+Xc6BCKt2IFE9JdZy7kmy4NOk+xZAXcuT57So)

# 数据库配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://${mysql.host}:3306/ems-thymeleaf?characterEncoding=UTF-8
username: ENC(SAYP3WbR7jm0HYPlg3b9IWvMEZBBK+M8+rmDcFrwH8p+WNwxyYWkuZ1FvZztGELF)
password: ENC(SAYP3WbR7jm0HYPlg3b9IWvMEZBBK+M8+rmDcFrwH8p+WNwxyYWkuZ1FvZztGELF)

# mybatis配置
mybatis:
mapper-locations: classpath:com/study/mapper/*.xml
type-aliases-package: com.study.entity

# jasypt加密配置 只要我们引完依赖做好配置就会给在我们工厂中形成一个类StringEncryptor 我们直接注入就可使用
jasypt:
encryptor:
algorithm: PBEWITHHMACSHA512ANDAES_256 # 指定加密算法 我们这里用默认算法不配置也可
# password: 123456 # 指定秘钥,秘钥一般不能暴露在外面,需要单独设置 -Djasypt.encryptor.password=123456
# 打包运行时添加加密秘钥:nohuo java -jar -Djasypt.encryptor.password=123456 xxx.jar

启动类添加注解扫描

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

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.study.dao")
public class SpringBootJasyptApplication {

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

}

新建实体类User

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
package com.study.entity;

/**
* @ClassName User
* @Description TODO
* @Author chabai
* @Date 2022/7/3 11:43
* @Version 1.0
*/
public class User {
private Integer id;
private String username;
private String realname;
private String password;
private Boolean gender;

public User() {
}

public User(Integer id, String username, String realname, String password, Boolean gender) {
this.id = id;
this.username = username;
this.realname = realname;
this.password = password;
this.gender = gender;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getRealname() {
return realname;
}

public void setRealname(String realname) {
this.realname = realname;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public Boolean getGender() {
return gender;
}

public void setGender(Boolean gender) {
this.gender = gender;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", realname='" + realname + '\'' +
", password='" + password + '\'' +
", gender=" + gender +
'}';
}
}

新建UserDAO

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.study.dao;

import com.study.entity.User;

import java.util.List;

/**
* @ClassName UserDAO
* @Description TODO
* @Author chabai
* @Date 2022/7/3 11:45
* @Version 1.0
*/
public interface UserDAO {
/**
* @MethodName findAll
* @Description 查询所有
* @return: java.util.List<com.study.entity.User>
* @Author chabai
* @Date 11:45 2022/7/3
*/
List<User> findAll();
}

新建UserDAOMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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.study.dao.UserDAO">
<!--findAll-->
<select id="findAll" resultType="User">
select
id,username,realname,password,gender
from `user`
</select>
</mapper>

新建UserDaoTests

1
测试dao是否正常 我们遵循一层一测

新建UserService

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

import com.study.entity.User;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* @ClassName UserService
* @Description TODO
* @Author chabai
* @Date 2022/7/3 11:47
* @Version 1.0
*/
public interface UserService {
/**
* @MethodName findAll
* @Description 查询所有
* @return: java.util.List<com.study.entity.User>
* @Author chabai
* @Date 11:48 2022/7/3
*/
List<User> findAll();
}

新建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
28
29
30
31
package com.study.service;

import com.study.dao.UserDAO;
import com.study.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
* @ClassName UserServiceImpl
* @Description TODO
* @Author chabai
* @Date 2022/7/3 11:48
* @Version 1.0
*/
@Service
@Transactional
public class UserServiceImpl implements UserService{
private UserDAO userDAO;
@Autowired
public UserServiceImpl(UserDAO userDAO) {
this.userDAO = userDAO;
}

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

新建UserController

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
package com.study.controller;

import com.study.entity.User;
import com.study.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* @ClassName UserController
* @Description TODO
* @Author chabai
* @Date 2022/7/3 11:50
* @Version 1.0
*/
@RestController
@RequestMapping("/users")
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping
public ResponseEntity<List<User>> users(){
List<User> users = userService.findAll();
return new ResponseEntity<>(users, HttpStatus.OK);
}
}

测试Jasypt加密算法

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
package com.study;

import org.jasypt.encryption.StringEncryptor;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
* @ClassName JasyptTest
* @Description TODO
* @Author chabai
* @Date 2022/7/3 11:59
* @Version 1.0
*/
@SpringBootTest
public class JasyptTest {

private StringEncryptor stringEncryptor;//引完依赖做好配置就会有的类
@Autowired
public JasyptTest(StringEncryptor stringEncryptor) {
this.stringEncryptor = stringEncryptor;
}

@Test
public void test(){
for (int i = 0; i < 10; i++) {
String root = stringEncryptor.encrypt("root");
System.out.println("root = " + root);//每次加密形成的加密结果都不同 但是每个我们都能用
}
}

@Test
public void test1(){
//加密
String localhost = stringEncryptor.encrypt("localhost");
System.out.println("localhost = " + localhost);
//解密
String decrypt = stringEncryptor.decrypt("+Szc93CJDBbbOZShvgnUxeZiec3+Xc6BCKt2IFE9JdZy7kmy4NOk+xZAXcuT57So");
System.out.println("decrypt = " + decrypt);
}

}

注意:启动前配置好秘钥参数:-Djasypt.encryptor.password=123456 测试哪块哪块加密钥 配置文件加密启动主程序也要给密钥才可

image-20240325143827355

启动项目进行测试:测试前配置传递秘钥参数,测试路径:http://localhost:8080/users

image-20240325143844516

image-20240325143859106

最终项目结构:

image-20240325143908917