自定义方式整合 Druid
# 490.自定义方式整合 Druid
默认情况下,使用的是 HikariDataSource,该框架性能也是不错的,但国内更常用的是 Druid,本篇来讲讲如何在 SpringBoot 中使用 Druid,因为它对有很完善的解决方案,例如有监控、防 SQL 注入攻击等功能
Druid 是开源的,托管在 GitHub (opens new window) 上。并且有很详细的中文文档:
在 SpringBoot 中,要使用第三方技术,要么是自定义,要么找有无相关的 starter,我们先看看自定义的情况下怎么用;
# 自定义使用
我们先引入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
2
3
4
5
然后我们新增一个配置类,用来注入 DataSource 对象:
package com.peterjxl.learnspringbootwebadmin.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class MyDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
注意,由于我们已经在配置文件中,配置了数据源链接信息,因此可以通过配置绑定的方式,来完成 Druid 数据源的配置。
然后就会使用我们的 Druid 数据源了。这是因为在配置类 DataSourceConfiguration
中,如果容器中有数据源了,那么根据条件装配,Hikari 就不会被放入容器中:
@ConditionalOnMissingBean(DataSource.class)
static class Hikari {
2
接下来我们测试下,打印下容器中的数据源类型:
package com.peterjxl.learnspringbootwebadmin;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Slf4j
@SpringBootTest
class LearnSpringBootWebAdminApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
DataSource dataSource;
@Test
void contextLoads() {
Long aLong = jdbcTemplate.queryForObject("select count(*) from students", Long.class);
log.info("记录总数:{}", aLong);
log.info("数据源类型:{}", dataSource.getClass());
}
}
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
运行结果:
数据源类型:class com.alibaba.druid.pool.DruidDataSource
# 配置监控页
我们打开文档,可以看到有如何配置监控:
简单来说,就是配置一个 Servlet 和访问路径,例如 web.xml:
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
2
3
4
5
6
7
8
9
根据配置中的 url-pattern 来访问内置监控页面,如果是上面的配置,内置监控页面的首页是/druid/index.html
而在 SpringBoot 中,我们刚刚学习了如何注入 Servlet:
@Configuration
public class MyDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
@Bean
public ServletRegistrationBean statViewServlet() {
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
return servletRegistrationBean;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
然后重启项目,访问 localhost: 9999/druid/index.html (opens new window),可以看到能正常访问:
# 设置登录
一般来说,Druid 监控页是不能被随意访问的,不然很容易暴露一些敏感信息。我们可以通过配置,要登录后才能访问。
文档:
# 1.2 配置监控页面访问密码
需要配置 Servlet 的
loginUsername
和loginPassword
这两个初始参数。具体可以参考: 为 Druid 监控配置访问权限(配置访问监控信息的用户与密码) (opens new window)
示例如下:
<!-- 配置 Druid 监控信息显示页面 --> <servlet> <servlet-name>DruidStatView</servlet-name> <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class> <init-param> <!-- 允许清空统计数据 --> <param-name>resetEnable</param-name> <param-value>true</param-value> </init-param> <init-param> <!-- 用户名 --> <param-name>loginUsername</param-name> <param-value>druid</param-value> </init-param> <init-param> <!-- 密码 --> <param-name>loginPassword</param-name> <param-value>druid</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>DruidStatView</servlet-name> <url-pattern>/druid/*</url-pattern> </servlet-mapping>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24# 2. 配置 allow 和 deny
StatViewSerlvet 展示出来的监控信息比较敏感,是系统运行的内部情况,如果你需要做访问控制,可以配置 allow 和 deny 这两个参数。比如:
<servlet> <servlet-name>DruidStatView</servlet-name> <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class> <init-param> <param-name>allow</param-name> <param-value>128.242.127.1/24,128.242.128.1</param-value> </init-param> <init-param> <param-name>deny</param-name> <param-value>128.242.127.4</param-value> </init-param> </servlet>
1
2
3
4
5
6
7
8
9
10
11
12
我们在 SpringBoot 中这样配置:
@Bean
public ServletRegistrationBean statViewServlet() {
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
servletRegistrationBean.addInitParameter("loginUsername", "admin");
servletRegistrationBean.addInitParameter("loginPassword", "123456");
return servletRegistrationBean;
}
2
3
4
5
6
7
8
重启,此时就需要登录才能看到监控信息:
# SQL 监控
我们可以试试 SQL 监控功能。先看看文档:
文档大意:
Druid 内置提供一个 StatFilter,用于统计监控信息。
# 1. 别名配置
StatFilter 的别名是 stat,这个别名映射配置信息保存在 druid-xxx.jar!/META-INF/druid-filter.properties。
在 spring 中使用别名配置方式如下:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> ... ... <property name="filters" value="stat" /> </bean>
1
2
3
4
也就是说,我们在数据源里要配置一个属性 filters,值是 stat。因此我们修改下注入 DataSource 时的属性:
@Configuration
public class MyDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setFilters("stat");
return druidDataSource;
}
//.....
2
3
4
5
6
7
8
9
10
11
然后我们新增一个查询请求:
@Controller
public class IndexController {
@Autowired
JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/sql")
public String queryFromDb() {
Long aLong = jdbcTemplate.queryForObject("select count(*) from students", Long.class);
return aLong.toString();
}
//..............
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
重启,访问/sql 路径,可以看到有结果了:
关于什么是时间分布,可以参考文档中关于连接池的介绍:
# 3.7 区间分布
SQL 监控项上,执行时间、读取行数、更新行数都有区间分布,将耗时分布成 8 个区间:
- 0 - 1 耗时 0 到 1 毫秒的次数
- 1 - 10 耗时 1 到 10 毫秒的次数
- 10 - 100 耗时 10 到 100 毫秒的次数
- 100 - 1,000 耗时 100 到 1000 毫秒的次数
- 1,000 - 10,000 耗时 1 到 10 秒的次数
- 10,000 - 100,000 耗时 10 到 100 秒的次数
- 100,000 - 1,000,000 耗时 100 到 1000 秒的次数
- 1,000,000 - 耗时 1000 秒以上的次数
记录耗时区间的发生次数,通过区分分布,可以很方便看出 SQL 运行的极好、普通和极差的分布。 耗时区分分布提供了“执行+RS 时分布”,是将执行时间+ResultSet 持有时间合并监控,这个能方便诊断返回行数过多的查询。
为了方便后续的配置,我们将 /sql 的路径放行下:
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有请求,包括静态资源
.excludePathPatterns("/", "/login", "/css/**", "/fonts/**", "/images/**", "/js/**", "/sql");
}
}
2
3
4
5
6
7
8
9
10
# 监控 web 应用
例如配置 web 应用监控:
其本质也是配置一个 Servlet:
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2
3
4
5
6
7
8
9
10
11
12
注意:经常需要排除一些不必要的 url,比如 .js,/css
等,这些配置在 init-param 中
在 SpringBoot 中,我们则通过注入的方式来配置:
@Bean
public FilterRegistrationBean webStatFilter() {
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> filterFilterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
filterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
filterFilterRegistrationBean.addInitParameter("exclusions", "*.js,*.css,/druid/*");
return filterFilterRegistrationBean;
}
2
3
4
5
6
7
8
重启项目,然后多访问几次 /sql 路径,可以看到 web 应用和 URI 监控都与数据了:
# SQL 注入(防火墙)
文档说明:
怎么配置防御 SQL 注入攻击:Druid 提供了 WallFilter,它是基于 SQL 语义分析来实现防御 SQL 注入攻击的。具体配置看这里: https://github.com/alibaba/druid/wiki/配置-wallfilter (opens new window)
使用缺省配置的 WallFilter
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> ... <property name="filters" value="wall"/> </bean>
1
2
3
4
WallFilter 可以结合其他 Filter 一起使用,例如:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> ... <property name="filters" value="wall,stat"/> </bean>
1
2
3
4
因此,我们可以这样改配置:
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setFilters("stat,wall");
return druidDataSource;
}
2
3
4
5
6
7
重启项目,然后多访问几次 /sql 路径,可以看到 SQL 防火墙有数据了:
# 简化配置
我们在注入 Servlet、Filter 的时候,调用了很多 set 方法,其实这些 set 方法,都是可以读取配置文件后自动 set 的:
spring:
datasource:
url: jdbc:mysql://localhost:3306/learnjdbc?serverTimezone=UTC
username: learn
password: learnpassword
driver-class-name: com.mysql.cj.jdbc.Driver
filters: stat,wall
2
3
4
5
6
7
然后方法中的 set 代码就可以注释掉了:
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
// druidDataSource.setFilters("stat,wall");
return druidDataSource;
}
2
3
4
5
6
7
# 最后
本文我们讲了如何自己整合 Druid,下一篇讲如何用 starter 的方式来整合
已将本文源码上传到 Gitee (opens new window) 或 GitHub (opens new window) 的分支 demo10,读者可以通过切换分支来查看本文的示例代码