48. SpringBoot

微服务

微服务介绍: 英文原文 中文翻译 或者 中文翻译

手动搭建 SpringBoot

代码地址

继承父工程

在 pom 文件中继承父工程

1
2
3
4
5
6
<!-- 所有的 SpringBoot 项目都需要继承 spring-boot-starter-parent 父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>

引用 web 模块

1
2
3
4
5
6
7
8
<!-- 引用 web 模块,引用了 SpringMVC,和 Tomcat 等 -->
<dependencies>
<!-- starter 被称为 “场景启动器” Springboot 提供了很多的 starter 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

添加 Controller

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

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloController {
@ResponseBody
@GetMapping("/hello")
public String hello(){
return "OK!+哈哈";
}
}

添加主程序

添加主程序,主程序必须在所有所有 package 的父 package 下面(在什么 controller 上面的一层包下,因为他会去默认扫描他的子包下面的类,让我加入 IOC 容器中)如果需要单独指定那么可以使用 @ComponentScan 注解。主程序启动会启动内置的 Tomcat。

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

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

// @ComponentScan("com.itguigu.com.springboot") 默认就会扫描主程序下面的子包,所以可以不加
// 声明当前项目为 SpringBoot 项目
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
// 启动内置的 Tomcat
SpringApplication.run(MainApplication.class, args);
}
}

访问测试

直接右键启动 MainApplication, 默认端口为 8080, 默认没有上下文路径,直接访问 http://localhost:8080/hello 即可。

启动 Springboot 项目

方式1:右键 Run As

方式2:Boot Dashboard

SpringBoot 相关模块

Spring boot 中有很多 starter,常见信息如下:

简化部署

将 spring boot 项目打成 jar 包进行部署

先引入打包插件

1
2
3
4
5
6
7
8
9
<!-- 引入springboot插件;打包插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

引入之后直接 maven 运行 package 进行打包

打完包之后,直接 java -jar + 包名称运行即可

联网创建 SpringBoot 项目

代码地址

自动创建出来的项目中会在 src/main/resource 下面有 static 和 templates 文件夹,以及 application.propertis 文件,static 文件夹的访问路径就在根下面(里面放一张 xx.jpg,直接使用 http://localhost:8080/xx.jpg 就能访问到。)

可以 application.propertis 中进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
# 修改端口号
server.port=8081

# 修改上下文路径(一般不指定)
# server.servlet.context-path=/a

# 修改session 超时时间,默认是 s
server.servlet.session.timeout=1800
# 设置 tomcat 并发(500就差不多了)
server.tomcat.max-threads=500
# 设置 url 编码问题(POST 请求 SpringBoot会自动配置编码过滤器,所以可以不管)
server.tomcat.uri-encoding=UTF-8

yml 配置文件

yml 配置的优先级低于 properties 属性文件。SpringBoot 使用一个全局的配置文件,配置文件名是固定的 application.properties 或者 application.yml。yml 文件使用缩进表示层级关系,缩进时不允许使用Tab键,只允许使用空格。缩进的空格数目不重要,只要相同层级的元素左侧对齐即可。大小写敏感。

yml 的配置格式是 k: v 形式,使用字面来书写【普通的值(数字,字符串,布尔)】,字符串默认不用加上单引号或者双引号。双引号不会转义字符串里面的特殊字符,单引号会转义特殊字符。更多配置可参考

SpringBoot 自动配置原理

SpringBoot 为什么能开箱即用呢?因为里面的所有东西都默认的配置好了。在 Maven Dependencies 中,有一个 spring-boot-autoconfigure-2.2.6.RELEASE.jar 的文件,里面的 MRTA-INF 中有一个 spring.factories 文件,其中的 Auto Configure 配置了所有的 Configuration 配置类。

这里以ThymeleafAutoConfiguration 为例子进行说明,查询该类,并进入

会看到类上有很多的注解,详细解释如下:

1
2
3
4
5
6
7
8
9
10
// 表示这是一个配置类
@Configuration
// 配置文件
@EnableConfigurationProperties(ThymeleafProperties.class)
// 只有存在 TemplateMode SpringTemplateEngine 的时候 ThymeleafAutoConfiguration 类才生效(所以我们需要在 pom 中引入 Thymeleafstarter,才能生效,否则没作用)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {
......
}

进入到 ThymeleafProperties 中查看相关配置,就能看到一些配置的默认值

总结如下:

  1. 主程序类标注了@SpringBootApplication 注解相当于标注了 @EnableAutoConfiguration,@EnableAutoConfiguration 开启 SpringBoot 的自动配置功能。
  2. SpringBoot帮我们配好了所有的场景
  3. SpringBoot中会有很多的 xxxxAutoConfigurarion(帮我们给容器中自动配好组件)
  4. xxxxAutoConfigurarion 给容器中配组件的时候,组件默认的属性一般都是从 xxxProperties中获取这些属性的值
  5. xxxProperties 是和配置文件绑定的(属性一一对应)
  6. 我们可以在 properties 文件或者 yml 文件中改掉这些默认配置

SpringBoot 整合 MyBatis(XML版)

代码地址

整合 MyBatis

联网创建项目,并导入对应依赖的 starter

这里选择 JDBC,MyBatis,MySQL,SpringWeb 这四个 Starter。

新建 application.yml 配置文件,内容如下:

1
2
3
4
5
6
7
8
9
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/zcw?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml

在 resource 目录下新建 mybatis 文件夹,并新建 mybatis-config.xml 配置文件和 mapper 子文件夹。mybatis-config.xml 中内容如下:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

</configuration>

在 com.itguigu.springboot 包下新建子包,controller,service,bean,mapper,service.impl。

在 bean 中添加 TAdmin 类,内容如下:

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.itguigu.springboot.bean;

public class TAdmin {
private Integer id;
private String loginacct;
private String userpswd;
private String username;
private String email;
private String createtime;

public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLoginacct() {
return loginacct;
}
public void setLoginacct(String loginacct) {
this.loginacct = loginacct == null ? null : loginacct.trim();
}
public String getUserpswd() {
return userpswd;
}
public void setUserpswd(String userpswd) {
this.userpswd = userpswd == null ? null : userpswd.trim();
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username == null ? null : username.trim();
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email == null ? null : email.trim();
}
public String getCreatetime() {
return createtime;
}
public void setCreatetime(String createtime) {
this.createtime = createtime == null ? null : createtime.trim();
}
}

controller 中新建 TAdminController, 内容如下:

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.itguigu.springboot.bean.TAdmin;
import com.itguigu.springboot.service.TAdminService;

@Controller
public class TAdminController {
@Autowired
TAdminService adminService;

@ResponseBody
@RequestMapping("/getTAdminById/{id}")
public TAdmin getTAdminById(@PathVariable("id") Integer id) {
return adminService.getTAdminById(id);
}
}

service 中添加以下接口:

1
2
3
4
5
6
7
8
package com.itguigu.springboot.service;

import com.itguigu.springboot.bean.TAdmin;

public interface TAdminService {
public TAdmin getTAdminById(Integer id);

}

service.impl 中添加以下实现

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.itguigu.springboot.bean.TAdmin;
import com.itguigu.springboot.mapper.TAdminMapper;
import com.itguigu.springboot.service.TAdminService;

@Service
public class TAdminServiceImpl implements TAdminService {
@Autowired
TAdminMapper tadminMapper;

@Override
public TAdmin getTAdminById(Integer id) {
return tadminMapper.getTAdminById(id);
}
}

mapper 中新增 mapper 接口

1
2
3
4
5
6
7
8
9
package com.itguigu.springboot.mapper;

import com.itguigu.springboot.bean.TAdmin;

public interface TAdminMapper {

TAdmin getTAdminById(Integer id);

}

mapper.xml 中内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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.itguigu.springboot.mapper.TAdminMapper">

<resultMap id="BaseResultMap" type="com.itguigu.springboot.bean.TAdmin">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="loginacct" jdbcType="VARCHAR" property="loginacct" />
<result column="userpswd" jdbcType="CHAR" property="userpswd" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="createtime" jdbcType="CHAR" property="createtime" />
</resultMap>


<!-- TAdmin getTAdminById(Integer id); -->
<select id="getTAdminById" resultMap="BaseResultMap">
select * from t_admin where id = #{id}
</select>

</mapper>

注意⚠️:要在主程序入口处加上 @MapperScan 用于扫描 mapper 中的包

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

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


@MapperScan("com.itguigu.springboot.mapper")
@SpringBootApplication
public class SpringbootMybatisXmlApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisXmlApplication.class, args);
}
}

启动项目后直接访问 http://localhost:8080/getTAdminById/1 即可。

添加事务

使用注解开启事务

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

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

// 启用声明式事务
@EnableTransactionManagement
// 扫描 mapper
@MapperScan("com.itguigu.springboot.mapper")
@SpringBootApplication
public class SpringbootMybatisXmlApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisXmlApplication.class, args);
}
}

在 service impl 中具体的类或者方法上添加注解 @Transactional 表示添加事务。如果不想单独在方法上设置还可以将注解设置在类上,这样类中的方法都会有这个注解(生效的时候采用就近原则,方法上有就使用方法上的,方法上没有就使用类上的)。同样可以设置事务的隔离级别,传播行为等。设置信息可参考 Transactional 属性)

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.itguigu.springboot.bean.TAdmin;
import com.itguigu.springboot.mapper.TAdminMapper;
import com.itguigu.springboot.service.TAdminService;

// 如果不想单独设置还可以将注解设置在类上,这样类中的方法都会有这个注解(生效的时候采用就近原则,方法上有就使用方法上的,方法上没有就使用类上的)
@Transactional(readOnly = true)
@Service
public class TAdminServiceImpl implements TAdminService {
@Autowired
TAdminMapper tadminMapper;

// 如果是非查询,还可以设置事务隔离级别,传播行为,rollbackFor 等
// @Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)

// 加上事务,因为是查询,所有只设置 readOnly = true 即可
// @Transactional(readOnly = true)
@Override
public TAdmin getTAdminById(Integer id) {
return tadminMapper.getTAdminById(id);
}
}

SpringBoot 整合 MyBatis(注解版)

代码地址

移除 mapper 配置文件

复制上面的项目,删除 resource 下的 mybatis 文件夹(也就是删除 mapper.xml 文件和 mybatis 配置文件)和 application.yml 中的 mybatis 相关配置。

com.itguigu.springboot.mapper 包下,mapper 接口的方法中添加各种操作注解,来进行相关操作。

1
2
3
4
5
6
7
8
9
10
11
package com.itguigu.springboot.mapper;

import org.apache.ibatis.annotations.Select;

import com.itguigu.springboot.bean.TAdmin;

public interface TAdminMapper {
// 去除 mapper.xml 后,直接使用注解方式添加对应操作。还支持 @Insert,@Delete,@Update 等注解
@Select("select * from t_admin where id=#{id}")
TAdmin getTAdminById(Integer id);
}

启动项目后,直接访问 http://localhost:8080/getTAdminById/1 即可。

整合 Druid 数据源

配置文件方式

SpringBoot 默认的数据源驱动是 class com.zaxxer.hikari.HikariDataSource

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

import javax.sql.DataSource;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringbootMybatisXmlApplicationTests {

@Autowired
DataSource dataSource;

@Test
void testDataSource() {
// 获取默认的 DataSource 的驱动
System.out.println(dataSource.getClass()); // class com.zaxxer.hikari.HikariDataSource
}
}

但是平时我们都使用 Druid,那么如何修改呢?

pom 文件引入 Druid

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>

yml 文件中指定 datasource 的 type 的 com.alibaba.druid.pool.DruidDataSource

1
2
3
4
5
6
7
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/zcw?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource

再次运行测试用例,打印的 DataSource 的驱动就为 class com.alibaba.druid.pool.DruidDataSource

Config 配置类方式

新建 config 包,并新建 DruidDataSourceConfig 配置类

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

import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.alibaba.druid.pool.DruidDataSource;

@Configuration
public class DruidDataSourceConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource dataSource() throws SQLException{
DruidDataSource dataSource = new DruidDataSource();
dataSource.setFilters("stat");
return dataSource;
}
}

去除之前 yml 中配置的datasource 的 type。运行测试用例,打印的 DataSource 的驱动为 class com.alibaba.druid.pool.DruidDataSource

整合 Druid 监控

直接将以下代码粘贴到 DruidDataSourceConfig 配置类中,访问 http://localhost:8080/druid 使用上面配置的账户名密码登录后就能看到监控信息。完整代码如下:

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

import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;

@Configuration
public class DruidDataSourceConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setFilters("stat");
return dataSource;
}

// 配置Druid的监控
// 1、配置一个管理后台的Servlet
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin");
initParams.put("loginPassword", "123456");
initParams.put("allow", "");// 默认就是允许所有访问
initParams.put("deny", "192.168.15.21");// 拒绝哪个ip访问
bean.setInitParameters(initParams);
return bean;
}

// 2、配置一个web监控的filter
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*");// 排除过滤
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}

整合三大 Web 组件

之前的Web开发基于Servlet 2.5规范(在web.xml中配置Servlet,Filter,Listener)现在基于Servlet 3.0规范(基于配置类的方式声明对象:@WebServlet @WebFilter @WebListener)。

注意⚠️:如果要让@WebServlet @WebFilter @WebListener 在 SpringBoot 中生效,需要在主程序的类上加上注解 @ServletComponentScan

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

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.transaction.annotation.EnableTransactionManagement;

// 扫描 @WebServlet @WebFilter @WebListener
@ServletComponentScan
// 启用声明式事务
@EnableTransactionManagement
// 扫描 mapper
@MapperScan("com.itguigu.springboot.mapper")
@SpringBootApplication
public class SpringbootMybatisXmlApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisXmlApplication.class, args);
}
}

Servlet

Servlet 以后我们在 springboot 中基本上不会用到的。但是还是介绍用法,servlet 的写法还是和之前一样的,直接继承 HttpServlet 然后重写 doget 方法。但是不需要在 web.xml 中进行配置,只需要在类上加上 @WebServlet(urlPatterns="/my") 注解即可。其中 /my 是请求路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.itguigu.springboot.web.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet{

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("MyServlet do.......");
}
}

Filter

Filter 在以后会用到。写法还是一样,实现 Filter 接口,重写 init,doFilter,destroy 方法即可。也不需要在 web.xml 中进行配置,直接加上 @WebFilter(urlPatterns="/*") 注解即可。

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

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

@WebFilter(urlPatterns = "/*")
public class HelloFilter implements Filter{

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("HelloFilter............放行之前");
chain.doFilter(request, response);
System.out.println("HelloFilter............放行之后");
}
}

Listener

Listener 在以后会用到。写法还是一样,实现 ServletContextListener 接口,重写 contextDestroyed,contextInitialized 方法即可。也不需要在 web.xml 中进行配置,直接加上 @WebListener 注解即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.itguigu.springboot.web.litener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class HelloListener implements ServletContextListener{
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("应用销毁了....HelloListener");
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("应用销毁了....HelloListener");
}
}