druid统计参数变化的代码实现
1、背景
源码阅读已有一周,阅读花费时间长的可能就两天吧,其他基本上没有看很久,周末分享实在拿不出啥东西来,所以选择一个小的点进行分享,以自己的体验为主。
2、环境
springboot + mysql + druid1.2.8 + maven3.6.3 + jdk1.8 + idea
本来是想着能否用servlet直接集成druid进行run和测试,由于时间关系,研究一半中断了,就采用平时用的多的springboot
3、目标说明
druid以监控统计著称,所以希望找到druid是怎么来加统计信息的,比如,insert之后统计页面上的执行数怎么发生变化的。当然也包括其他的比如查询、sql记录、执行时间、这些统计数据怎么存储等这些
4、开始跑项目
代码很简单,配置好application.yaml先:
spring:
datasource:
username: root
password: root
#serverTimezone=UTC 配置时区
url: jdbc:mysql://127.0.0.1:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
注册ServletRegistrationBean和FilterRegistrationBean
package com.test.druidstat;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.AbstractWebStatImpl;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid(){
return new DruidDataSource();
}
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
Map<String,String> initParams = new HashMap<>();
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
initParams.put("allow",""); //默认就是允许所有访问
//deny:Druid 后台拒绝谁访问,表示禁止此ip访问
// initParams.put("deny","192.168.10.132");
bean.setInitParameters(initParams);
return bean;
}
@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;
}
}
添加测试类:
package com.test.druidstat;
import com.alibaba.druid.stat.DruidStatManagerFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
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;
import java.util.Map;
@RestController
@RequestMapping("/")
public class TestController {
@Autowired
private JdbcTemplate jdbcTemplate;
@GetMapping("userList")
public List<Map<String, Object>> userList(){
String sql = "select * from user_innodb limit 1, 10";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
@GetMapping("insert")
public int insert(){
String sql = "insert into user_innodb (name, gender, phone) values ('test', '1', '13813813811')";
return jdbcTemplate.update(sql);
}
@GetMapping("/stat")
public Object druidStat() {
// DruidStatManagerFacade#getDataSourceStatDataList 该方法可以获取所有数据源的监控数据
return DruidStatManagerFacade.getInstance().getDataSourceStatDataList();
}
}
先访问http://127.0.0.1:8080/druid登陆控制台:
访问http://127.0.0.1:8080/userList和http://127.0.0.1:8080/insert进行测试:
会发现控制台的sql监控会发生变化,利用测试的请求断点调试。
5、走的弯路
打断点发现,堆栈信息非常大,很难找到具体的实现在哪:
就想先找找请求链接实现在哪:
先看控制台的请求:
使用双击shift查找/sql.json
不幸的是并没有找到任何东西,无奈只能去看源码目录找长得像的,发现MonitorStatService.java:
然而在集成druid中jar内并没有这个package和class:(没有com.alibaba.druid.admin这个包)
无奈,只能看别的类,最后发现了TableStat类,看到了insertCount以为知道了字段(因为先测试的插入sql):
然而打断点并没有进来!
6、初步发现端倪
经过上述的尝试可以发现,插入sql带来改变的变量并不是insertCount,因为那是唯一一个增长的方法。所以盲目靠猜和看的办法行不通,只能回到断点调试,仔细观察,最终将范围缩小定位到WebStatFilter:
最后定位到this.webAppStat.afterInvoke():
可以看到具体的变量改变出来了
继续跟踪:
发现初始化统计参数是在beforeInvoke实现的,并且是先存在sessionStatMap中,并能够知道是通过LRU缓存来获取的:
可以看到具体的变量改变出来了
继续跟踪:
发现初始化统计参数是在beforeInvoke实现的,并且是先存在sessionStatMap中,并能够知道是通过LRU缓存来获取的:
后续再研究!