一、Actuator端点安全过滤深度解析
1.1 端点安全架构概览
java
复制
下载
// Actuator安全架构核心组件
┌─────────────────────────────────────────────────────────────┐
│ 应用层安全配置 │
├─────────────────────────────────────────────────────────────┤
│ @Configuration │
│ public class ActuatorSecurityConfig │
│ extends WebSecurityConfigurerAdapter { │
│ // 1. 端点角色控制 │
│ // 2. IP白名单过滤 │
│ // 3. 请求头验证 │
│ } │
├─────────────────────────────────────────────────────────────┤
│ 过滤器链 (Filter Chain) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ CORS │ │ 认证 │ │ 授权 │ │
│ │ Filter │ │ Filter │ │ Filter │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 端点访问控制矩阵 │
│ ┌─────────────────┬─────────────┬─────────────┐ │
│ │ 端点 │ 敏感度 │ 所需角色 │ │
│ ├─────────────────┼─────────────┼─────────────┤ │
│ │ /actuator/health│ 低 │ PUBLIC │ │
│ │ /actuator/info │ 低 │ PUBLIC │ │
│ │ /actuator/metrics│ 中 │ MONITOR │ │
│ │ /actuator/env │ 高 │ ADMIN │ │
│ │ /actuator/loggers│ 高 │ ADMIN │ │
│ └─────────────────┴─────────────┴─────────────┘ │
└─────────────────────────────────────────────────────────────┘
1.2 端点安全配置实现
java
复制
下载
// 完整端点安全配置类
@Configuration
@Order(1) // 高优先级,先于应用安全配置
public class ActuatorSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Value("${management.endpoints.web.exposure.include:health,info}")
private String[] exposedEndpoints;
@Value("${management.endpoints.web.base-path:/actuator}")
private String actuatorBasePath;
@Value("${security.actuator.ips:}")
private String[] allowedIps;
@Autowired
private ActuatorEndpointProperties endpointProperties;
// 自定义请求头验证器
@Bean
public ActuatorHeaderValidator headerValidator() {
return new ActuatorHeaderValidator();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 1. 构建端点访问规则
List<EndpointAccessRule> rules = buildAccessRules();
// 2. 配置安全规则
http
// 匹配Actuator端点路径
.antMatcher(actuatorBasePath + "/**")
.authorizeRequests()
// 健康检查端点 - 完全公开
.antMatchers(HttpMethod.GET,
actuatorBasePath + "/health",
actuatorBasePath + "/health/**")
.permitAll()
// 信息端点 - 基础认证
.antMatchers(HttpMethod.GET, actuatorBasePath + "/info")
.hasAnyRole("USER", "MONITOR", "ADMIN")
// 指标端点 - 监控角色
.antMatchers(HttpMethod.GET, actuatorBasePath + "/metrics")
.hasRole("MONITOR")
.antMatchers(HttpMethod.GET, actuatorBasePath + "/metrics/**")
.hasRole("MONITOR")
// 环境变量端点 - 管理员角色
.antMatchers(actuatorBasePath + "/env")
.hasRole("ADMIN")
.antMatchers(actuatorBasePath + "/env/**")
.hasRole("ADMIN")
// 日志级别端点 - 管理员角色
.antMatchers(actuatorBasePath + "/loggers")
.hasRole("ADMIN")
.antMatchers(actuatorBasePath + "/loggers/**")
.hasRole("ADMIN")
// 配置属性端点 - 管理员角色
.antMatchers(actuatorBasePath + "/configprops")
.hasRole("ADMIN")
// Bean信息端点 - 管理员角色
.antMatchers(actuatorBasePath + "/beans")
.hasRole("ADMIN")
// 线程转储端点 - 管理员角色
.antMatchers(actuatorBasePath + "/threaddump")
.hasRole("ADMIN")
// 堆转储端点 - 管理员角色
.antMatchers(actuatorBasePath + "/heapdump")
.hasRole("ADMIN")
// 所有其他端点默认拒绝
.anyRequest().denyAll()
.and()
// 3. 添加自定义过滤器
.addFilterBefore(new ActuatorIPFilter(allowedIps),
BasicAuthenticationFilter.class)
.addFilterBefore(new ActuatorHeaderFilter(headerValidator()),
BasicAuthenticationFilter.class)
// 4. 配置HTTP Basic认证
.httpBasic()
.realmName("Actuator")
.authenticationEntryPoint(new ActuatorAuthenticationEntryPoint())
.and()
// 5. 禁用CSRF(API端点通常不需要)
.csrf().disable()
// 6. 配置会话管理
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 7. 添加安全头
.headers()
.contentSecurityPolicy("default-src 'self'")
.frameOptions().deny()
.xssProtection().block(true);
// 8. 动态配置基于属性的端点暴露
configureExposedEndpoints(http);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 配置Actuator专用的内存用户存储
auth.inMemoryAuthentication()
.withUser("actuator-monitor")
.password(passwordEncoder().encode("monitor-secret"))
.roles("MONITOR")
.and()
.withUser("actuator-admin")
.password(passwordEncoder().encode("admin-secret"))
.roles("ADMIN", "MONITOR")
.and()
.withUser("app-user")
.password(passwordEncoder().encode("user-secret"))
.roles("USER");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 构建端点访问规则
*/
private List<EndpointAccessRule> buildAccessRules() {
List<EndpointAccessRule> rules = new ArrayList<>();
// 从配置文件中读取端点敏感度配置
Map<String, EndpointSensitivity> sensitivityMap =
endpointProperties.getSensitivity();
for (Map.Entry<String, EndpointSensitivity> entry : sensitivityMap.entrySet()) {
String endpointId = entry.getKey();
EndpointSensitivity sensitivity = entry.getValue();
EndpointAccessRule rule = new EndpointAccessRule(
endpointId,
sensitivity.getLevel(),
sensitivity.getRequiredRoles(),
sensitivity.getAllowedMethods(),
sensitivity.isRequireSsl()
);
rules.add(rule);
}
return rules;
}
/**
* 动态配置暴露的端点
*/
private void configureExposedEndpoints(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry =
http.authorizeRequests();
// 根据配置动态添加端点访问规则
for (String endpoint : exposedEndpoints) {
String pattern = actuatorBasePath + "/" + endpoint;
String patternWildcard = pattern + "/**";
// 设置端点访问权限
registry
.antMatchers(HttpMethod.GET, pattern)
.hasRole(getRequiredRole(endpoint))
.antMatchers(HttpMethod.GET, patternWildcard)
.hasRole(getRequiredRole(endpoint));
}
}
/**
* 获取端点所需角色
*/
private String getRequiredRole(String endpointId) {
switch (endpointId.toLowerCase()) {
case "health":
case "info":
return "USER";
case "metrics":
case "prometheus":
return "MONITOR";
case "env":
case "loggers":
case "configprops":
case "beans":
case "threaddump":
case "heapdump":
return "ADMIN";
default:
return "ADMIN"; // 默认需要管理员权限
}
}
// 自定义IP过滤器
public class ActuatorIPFilter extends OncePerRequestFilter {
private final Set<String> allowedIpRanges;
public ActuatorIPFilter(String[] allowedIps) {
this.allowedIpRanges = Arrays.stream(allowedIps)
.collect(Collectors.toSet());
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String clientIp = getClientIpAddress(request);
// 检查IP是否在白名单中
if (!isIpAllowed(clientIp)) {
log.warn("Blocked Actuator access from unauthorized IP: {}", clientIp);
// 记录安全事件
securityEventLogger.logUnauthorizedAccess(
clientIp,
request.getRequestURI(),
"IP_NOT_ALLOWED"
);
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().write("Access denied: IP not allowed");
return;
}
filterChain.doFilter(request, response);
}
private String getClientIpAddress(HttpServletRequest request) {
String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader != null) {
return xfHeader.split(",")[0];
}
return request.getRemoteAddr();
}
private boolean isIpAllowed(String ip) {
if (allowedIpRanges.isEmpty()) {
return true; // 没有配置IP白名单,允许所有
}
for (String allowedRange : allowedIpRanges) {
if (ipMatchesRange(ip, allowedRange)) {
return true;
}
}
return false;
}
private boolean ipMatchesRange(String ip, String range) {
// 支持CIDR格式: 192.168.1.0/24
// 支持通配符: 192.168.1.*
// 支持单个IP: 192.168.1.100
if (range.contains("/")) {
return isInSubnet(ip, range);
} else if (range.contains("*")) {
String regex = range.replace(".", "\\.").replace("*", ".*");
return ip.matches(regex);
} else {
return ip.equals(range);
}
}
}
// 自定义请求头过滤器
public class ActuatorHeaderFilter extends OncePerRequestFilter {
private final ActuatorHeaderValidator validator;
public ActuatorHeaderFilter(ActuatorHeaderValidator validator) {
this.validator = validator;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
// 验证自定义安全头
if (!validator.validateHeaders(request)) {
log.warn("Invalid security headers for Actuator request");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return;
}
// 添加响应头
response.setHeader("X-Actuator-Access", "secured");
response.setHeader("X-Content-Type-Options", "nosniff");
filterChain.doFilter(request, response);
}
}
}
// 端点访问规则实体
@Data
@AllArgsConstructor
class EndpointAccessRule {
private String endpointId;
private SensitivityLevel sensitivity;
private Set<String> requiredRoles;
private Set<HttpMethod> allowedMethods;
private boolean requireSsl;
enum SensitivityLevel {
PUBLIC, // 公开访问
RESTRICTED, // 受限访问
SENSITIVE, // 敏感信息
CRITICAL // 关键系统
}
}
// 请求头验证器
@Component
class ActuatorHeaderValidator {
@Value("${security.actuator.header.name:X-Actuator-Token}")
private String headerName;
@Value("${security.actuator.header.value:}")
private String expectedValue;
@Value("${security.actuator.header.enabled:false}")
private boolean enabled;
public boolean validateHeaders(HttpServletRequest request) {
if (!enabled) {
return true;
}
String headerValue = request.getHeader(headerName);
if (headerValue == null) {
log.debug("Missing required header: {}", headerName);
return false;
}
// 支持JWT Token验证
if (headerValue.startsWith("Bearer ")) {
return validateJwtToken(headerValue.substring(7));
}
// 简单Token验证
return headerValue.equals(expectedValue);
}
private boolean validateJwtToken(String token) {
try {
// 验证JWT Token
Jwt jwt = jwtDecoder.decode(token);
// 检查权限
Collection<String> authorities = jwt.getClaimAsStringList("authorities");
return authorities.contains("ACTUATOR_ACCESS");
} catch (Exception e) {
log.warn("Invalid JWT token for Actuator access", e);
return false;
}
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
1.3 端点敏感信息过滤
java
复制
下载
// 端点响应过滤器 - 防止敏感信息泄露
@Component
public class ActuatorResponseFilter implements Filter {
@Autowired
private ObjectMapper objectMapper;
@Value("${management.endpoint.sensitive.masking.enabled:true}")
private boolean maskingEnabled;
// 需要过滤的敏感关键词
private static final Set<String> SENSITIVE_KEYS = Set.of(
"password", "secret", "key", "token", "credential",
"private", "authorization", "apikey", "jwt"
);
// 需要完全隐藏的端点路径
private static final Set<String> SENSITIVE_ENDPOINTS = Set.of(
"/actuator/env",
"/actuator/configprops",
"/actuator/beans"
);
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 创建响应包装器
ContentCachingResponseWrapper responseWrapper =
new ContentCachingResponseWrapper(httpResponse);
chain.doFilter(request, responseWrapper);
// 获取响应内容
byte[] responseBody = responseWrapper.getContentAsByteArray();
if (responseBody.length > 0) {
// 处理响应内容
String filteredBody = filterSensitiveContent(
httpRequest.getRequestURI(),
new String(responseBody, responseWrapper.getCharacterEncoding())
);
// 写入过滤后的响应
responseWrapper.resetBuffer();
responseWrapper.getWriter().write(filteredBody);
}
responseWrapper.copyBodyToResponse();
}
/**
* 过滤敏感内容
*/
private String filterSensitiveContent(String requestUri, String originalContent) {
if (!maskingEnabled) {
return originalContent;
}
try {
// 根据端点类型应用不同的过滤策略
if (SENSITIVE_ENDPOINTS.contains(requestUri)) {
return filterSensitiveEndpoints(originalContent);
} else if (requestUri.startsWith("/actuator/env/")) {
return filterEnvEndpoint(originalContent, requestUri);
}
return originalContent;
} catch (Exception e) {
log.error("Failed to filter sensitive content", e);
return originalContent; // 发生异常时返回原始内容
}
}
/**
* 过滤敏感端点响应
*/
private String filterSensitiveEndpoints(String content) throws JsonProcessingException {
JsonNode rootNode = objectMapper.readTree(content);
if (rootNode.isObject()) {
ObjectNode filteredNode = filterObjectNode((ObjectNode) rootNode);
return objectMapper.writeValueAsString(filteredNode);
}
return content;
}
/**
* 过滤环境变量端点
*/
private String filterEnvEndpoint(String content, String requestUri)
throws JsonProcessingException {
// 提取请求的属性名
String propertyName = extractPropertyNameFromUri(requestUri);
JsonNode rootNode = objectMapper.readTree(content);
if (rootNode.has("property") && rootNode.has("value")) {
String property = rootNode.get("property").asText();
String value = rootNode.get("value").asText();
// 检查是否为敏感属性
if (isSensitiveProperty(property)) {
ObjectNode maskedNode = objectMapper.createObjectNode();
maskedNode.put("property", property);
maskedNode.put("value", "***MASKED***");
maskedNode.put("sensitive", true);
return objectMapper.writeValueAsString(maskedNode);
}
}
return content;
}
/**
* 递归过滤对象节点
*/
private ObjectNode filterObjectNode(ObjectNode node) {
ObjectNode filteredNode = objectMapper.createObjectNode();
Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> field = fields.next();
String fieldName = field.getKey();
JsonNode fieldValue = field.getValue();
if (isSensitiveKey(fieldName)) {
// 敏感字段,进行脱敏
filteredNode.put(fieldName, "***MASKED***");
} else if (fieldValue.isObject()) {
// 递归过滤子对象
filteredNode.set(fieldName, filterObjectNode((ObjectNode) fieldValue));
} else if (fieldValue.isArray()) {
// 过滤数组
ArrayNode filteredArray = filterArrayNode((ArrayNode) fieldValue);
filteredNode.set(fieldName, filteredArray);
} else {
// 非敏感字段,直接复制
filteredNode.set(fieldName, fieldValue);
}
}
return filteredNode;
}
/**
* 过滤数组节点
*/
private ArrayNode filterArrayNode(ArrayNode arrayNode) {
ArrayNode filteredArray = objectMapper.createArrayNode();
for (JsonNode element : arrayNode) {
if (element.isObject()) {
filteredArray.add(filterObjectNode((ObjectNode) element));
} else {
filteredArray.add(element);
}
}
return filteredArray;
}
/**
* 判断是否为敏感键
*/
private boolean isSensitiveKey(String key) {
String lowerKey = key.toLowerCase();
return SENSITIVE_KEYS.stream().anyMatch(lowerKey::contains);
}
/**
* 判断是否为敏感属性
*/
private boolean isSensitiveProperty(String property) {
// 检查常见的敏感属性模式
return property.toLowerCase().matches(".*(password|secret|key|token|credential).*") ||
property.endsWith(".password") ||
property.endsWith(".secret") ||
property.contains("api.key") ||
property.contains("jwt.secret");
}
/**
* 从URI中提取属性名
*/
private String extractPropertyNameFromUri(String uri) {
// 格式: /actuator/env/property.sources[0].propertyName
String prefix = "/actuator/env/";
if (uri.startsWith(prefix)) {
return uri.substring(prefix.length());
}
return "";
}
}
// 端点访问审计
@Component
public class ActuatorAccessAudit {
@Autowired
private AuditEventRepository auditEventRepository;
@EventListener
public void handleAuthenticationSuccess(AuthenticationSuccessEvent event) {
Authentication auth = event.getAuthentication();
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
// 只记录Actuator端点的访问
if (request.getRequestURI().startsWith("/actuator")) {
AuditEvent auditEvent = new AuditEvent(
auth.getName(),
"ACTUATOR_ACCESS",
buildAuditData(request, auth)
);
auditEventRepository.add(auditEvent);
log.info("Actuator access granted: user={}, endpoint={}, roles={}",
auth.getName(),
request.getRequestURI(),
auth.getAuthorities()
);
}
}
@EventListener
public void handleAuthenticationFailure(AuthenticationFailureBadCredentialsEvent event) {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
if (request.getRequestURI().startsWith("/actuator")) {
log.warn("Failed Actuator access attempt: endpoint={}, ip={}",
request.getRequestURI(),
request.getRemoteAddr()
);
// 记录失败尝试(可用于实现锁定策略)
recordFailedAttempt(request.getRemoteAddr());
}
}
private Map<String, Object> buildAuditData(HttpServletRequest request,
Authentication auth) {
Map<String, Object> data = new HashMap<>();
data.put("endpoint", request.getRequestURI());
data.put("method", request.getMethod());
data.put("ip", request.getRemoteAddr());
data.put("userAgent", request.getHeader("User-Agent"));
data.put("timestamp", Instant.now());
data.put("roles", auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
return data;
}
private void recordFailedAttempt(String ip) {
// 实现IP失败次数记录,可用于自动锁定
// ...
}
}
二、JMX导出原理深度解析
2.1 JMX架构与核心组件
java
复制
下载
// Spring Boot JMX导出架构
┌─────────────────────────────────────────────────────────────┐
│ Spring Boot Application │
├─────────────────────────────────────────────────────────────┤
│ @SpringBootApplication │
│ public class Application { │
│ @Bean │
│ public EndpointMBeanExporter mbeanExporter() { │
│ return new EndpointMBeanExporter(); │
│ } │
│ } │
├─────────────────────────────────────────────────────────────┤
│ JMX基础设施层 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ MBeanServer (平台MBean服务器) │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ MBean Exporter (Bean导出器) │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ JMX Connector (连接器) │ │
│ │ - RMI │ │
│ │ - JMXMP │ │
│ │ - HTML Adaptor │ │
│ └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 监控工具层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ JConsole │ │ VisualVM │ │ JMXTool │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.2 JMX MBean导出器实现
java
复制
下载
// 自定义Endpoint MBean导出器
@Component
public class EndpointMBeanExporter implements ApplicationListener<ApplicationReadyEvent> {
private static final String MBEAN_DOMAIN = "com.example.actuator";
@Autowired
private MBeanServer mBeanServer;
@Autowired
private ApplicationContext applicationContext;
// 导出的MBean映射
private final Map<String, ObjectName> registeredMBeans = new ConcurrentHashMap<>();
// 端点操作映射
private final Map<String, EndpointOperation> endpointOperations = new HashMap<>();
@PostConstruct
public void init() {
// 初始化端点操作映射
endpointOperations.put("health", new HealthEndpointOperation());
endpointOperations.put("metrics", new MetricsEndpointOperation());
endpointOperations.put("info", new InfoEndpointOperation());
endpointOperations.put("env", new EnvEndpointOperation());
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
try {
// 1. 导出健康检查端点
exportHealthEndpoint();
// 2. 导出指标端点
exportMetricsEndpoint();
// 3. 导出信息端点
exportInfoEndpoint();
// 4. 导出环境变量端点
exportEnvEndpoint();
// 5. 导出自定义业务MBean
exportBusinessMBeans();
// 6. 注册JMX通知监听器
registerNotificationListeners();
log.info("Successfully exported {} MBeans to JMX", registeredMBeans.size());
} catch (Exception e) {
log.error("Failed to export MBeans to JMX", e);
}
}
/**
* 导出健康检查端点
*/
private void exportHealthEndpoint() throws Exception {
HealthEndpointMBean healthMBean = new HealthEndpointMBean();
ObjectName objectName = new ObjectName(MBEAN_DOMAIN + ":type=Endpoint,name=Health");
mBeanServer.registerMBean(healthMBean, objectName);
registeredMBeans.put("health", objectName);
log.debug("Exported Health endpoint as MBean: {}", objectName);
}
/**
* 导出指标端点
*/
private void exportMetricsEndpoint() throws Exception {
MetricsEndpointMBean metricsMBean = new MetricsEndpointMBean();
ObjectName objectName = new ObjectName(MBEAN_DOMAIN + ":type=Endpoint,name=Metrics");
mBeanServer.registerMBean(metricsMBean, objectName);
registeredMBeans.put("metrics", objectName);
log.debug("Exported Metrics endpoint as MBean: {}", objectName);
}
/**
* 健康检查端点MBean
*/
public class HealthEndpointMBean implements HealthEndpointMBeanMBean {
@Autowired
private HealthEndpoint healthEndpoint;
@Override
public String getHealthStatus() {
Health health = healthEndpoint.health();
return health.getStatus().toString();
}
@Override
public Map<String, Object> getHealthDetails() {
Health health = healthEndpoint.health();
return health.getDetails();
}
@Override
public boolean isDatabaseHealthy() {
Health health = healthEndpoint.health();
if (health.getDetails().containsKey("db")) {
Map<String, Object> dbDetails = (Map<String, Object>) health.getDetails().get("db");
return "UP".equals(dbDetails.get("status"));
}
return false;
}
@Override
public boolean isRedisHealthy() {
Health health = healthEndpoint.health();
if (health.getDetails().containsKey("redis")) {
Map<String, Object> redisDetails = (Map<String, Object>) health.getDetails().get("redis");
return "UP".equals(redisDetails.get("status"));
}
return false;
}
@Override
public void refreshHealth() {
// 触发健康检查刷新
// 实际实现中可能需要调用某些刷新机制
}
}
/**
* 指标端点MBean
*/
public class MetricsEndpointMBean implements MetricsEndpointMBeanMBean {
@Autowired
private MetricsEndpoint metricsEndpoint;
private final Map<String, MetricSample> metricCache = new ConcurrentHashMap<>();
private long lastCacheUpdate = 0;
private static final long CACHE_TTL = 5000; // 5秒缓存
@Override
public long getJvmMemoryUsed() {
return getMetricValue("jvm.memory.used");
}
@Override
public long getJvmMemoryMax() {
return getMetricValue("jvm.memory.max");
}
@Override
public double getSystemCpuUsage() {
return getMetricValue("system.cpu.usage");
}
@Override
public long getHttpRequestsTotal() {
return getMetricValue("http.server.requests", tags ->
tags.containsKey("method") && tags.get("method").equals("GET")
);
}
@Override
public double getHttpRequestErrorRate() {
long totalRequests = getMetricValue("http.server.requests");
long errorRequests = getMetricValue("http.server.requests", tags ->
tags.containsKey("status") &&
Integer.parseInt(tags.get("status")) >= 400
);
if (totalRequests == 0) {
return 0.0;
}
return (double) errorRequests / totalRequests * 100;
}
@Override
public Map<String, Double> getCustomMetrics() {
Map<String, Double> customMetrics = new HashMap<>();
// 获取自定义业务指标
List<String> metricNames = metricsEndpoint.listNames().getNames();
for (String metricName : metricNames) {
if (metricName.startsWith("custom.")) {
double value = getMetricValue(metricName);
customMetrics.put(metricName, value);
}
}
return customMetrics;
}
@Override
public void resetMetric(String metricName) {
// 重置特定指标
// 注意:不是所有指标都支持重置
log.info("Resetting metric: {}", metricName);
}
/**
* 获取指标值(带缓存)
*/
private double getMetricValue(String metricName) {
return getMetricValue(metricName, tags -> true);
}
private double getMetricValue(String metricName, Predicate<Map<String, String>> tagFilter) {
// 检查缓存
String cacheKey = metricName + tagFilter.hashCode();
MetricSample cached = metricCache.get(cacheKey);
if (cached != null &&
System.currentTimeMillis() - cached.timestamp < CACHE_TTL) {
return cached.value;
}
// 从端点获取指标
try {
Metric<?> metric = metricsEndpoint.metric(metricName, null);
if (metric != null) {
// 过滤和计算指标值
double value = calculateMetricValue(metric, tagFilter);
// 更新缓存
metricCache.put(cacheKey, new MetricSample(value, System.currentTimeMillis()));
return value;
}
} catch (Exception e) {
log.warn("Failed to get metric: {}", metricName, e);
}
return 0.0;
}
private double calculateMetricValue(Metric<?> metric, Predicate<Map<String, String>> tagFilter) {
// 根据指标类型计算值
if (metric instanceof Gauge) {
return ((Gauge<?>) metric).value();
} else if (metric instanceof Counter) {
return ((Counter) metric).count();
} else if (metric instanceof Timer) {
Timer timer = (Timer) metric;
return timer.mean(timer.baseTimeUnit());
} else if (metric instanceof DistributionSummary) {
DistributionSummary summary = (DistributionSummary) metric;
return summary.mean();
}
return 0.0;
}
static class MetricSample {
final double value;
final long timestamp;
MetricSample(double value, long timestamp) {
this.value = value;
this.timestamp = timestamp;
}
}
}
/**
* 自定义业务MBean导出
*/
private void exportBusinessMBeans() throws Exception {
// 1. 导出数据库连接池MBean
exportDataSourceMBean();
// 2. 导出缓存MBean
exportCacheMBean();
// 3. 导出线程池MBean
exportThreadPoolMBean();
// 4. 导出消息队列MBean
exportMessageQueueMBean();
}
/**
* 导出数据源MBean
*/
private void exportDataSourceMBean() throws Exception {
Map<String, DataSource> dataSources = applicationContext.getBeansOfType(DataSource.class);
for (Map.Entry<String, DataSource> entry : dataSources.entrySet()) {
String beanName = entry.getKey();
DataSource dataSource = entry.getValue();
if (dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource) {
// Tomcat JDBC连接池
exportTomcatDataSourceMBean(beanName, (org.apache.tomcat.jdbc.pool.DataSource) dataSource);
} else if (dataSource instanceof com.zaxxer.hikari.HikariDataSource) {
// HikariCP连接池
exportHikariDataSourceMBean(beanName, (com.zaxxer.hikari.HikariDataSource) dataSource);
}
}
}
private void exportTomcatDataSourceMBean(String beanName,
org.apache.tomcat.jdbc.pool.DataSource dataSource)
throws Exception {
TomcatDataSourceMBean mbean = new TomcatDataSourceMBean(dataSource);
ObjectName objectName = new ObjectName(
MBEAN_DOMAIN + ":type=DataSource,name=" + beanName
);
mBeanServer.registerMBean(mbean, objectName);
registeredMBeans.put("datasource." + beanName, objectName);
}
private void exportHikariDataSourceMBean(String beanName,
com.zaxxer.hikari.HikariDataSource dataSource)
throws Exception {
HikariDataSourceMBean mbean = new HikariDataSourceMBean(dataSource);
ObjectName objectName = new ObjectName(
MBEAN_DOMAIN + ":type=DataSource,name=" + beanName
);
mBeanServer.registerMBean(mbean, objectName);
registeredMBeans.put("datasource." + beanName, objectName);
}
/**
* Tomcat数据源MBean
*/
public class TomcatDataSourceMBean implements TomcatDataSourceMBeanMBean {
private final org.apache.tomcat.jdbc.pool.DataSource dataSource;
public TomcatDataSourceMBean(org.apache.tomcat.jdbc.pool.DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public int getActiveConnections() {
return dataSource.getActive();
}
@Override
public int getIdleConnections() {
return dataSource.getIdle();
}
@Override
public int getMaxActiveConnections() {
return dataSource.getMaxActive();
}
@Override
public int getWaitCount() {
return dataSource.getWaitCount();
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
@Override
public void resetStat() {
dataSource.resetStat();
}
@Override
public String getConnectionPoolConfiguration() {
return String.format(
"Active: %d, Idle: %d, MaxActive: %d, MaxIdle: %d, MinIdle: %d",
dataSource.getActive(),
dataSource.getIdle(),
dataSource.getMaxActive(),
dataSource.getMaxIdle(),
dataSource.getMinIdle()
);
}
}
/**
* 注册JMX通知监听器
*/
private void registerNotificationListeners() {
// 注册健康状态变化监听器
NotificationListener healthListener = (notification, handback) -> {
if (notification.getType().equals("com.example.health.status.change")) {
log.warn("Health status changed: {}", notification.getMessage());
// 发送告警通知
sendHealthAlert(notification.getMessage());
}
};
// 为所有MBean添加监听器
for (ObjectName objectName : registeredMBeans.values()) {
try {
mBeanServer.addNotificationListener(objectName, healthListener, null, null);
} catch (Exception e) {
log.warn("Failed to add notification listener for: {}", objectName, e);
}
}
}
/**
* JMX连接器配置
*/
@Configuration
@ConditionalOnProperty(name = "management.endpoints.jmx.enabled", havingValue = "true")
public static class JmxConnectorConfiguration {
@Value("${management.endpoints.jmx.port:1099}")
private int jmxPort;
@Value("${management.endpoints.jmx.host:localhost}")
private String jmxHost;
@Value("${management.endpoints.jmx.authentication.enabled:true}")
private boolean authenticationEnabled;
@Value("${management.endpoints.jmx.ssl.enabled:false}")
private boolean sslEnabled;
@Bean(initMethod = "start", destroyMethod = "stop")
public ConnectorServerFactoryBean connectorServer() throws Exception {
ConnectorServerFactoryBean connectorServer = new ConnectorServerFactoryBean();
// 构建服务URL
String serviceUrl = String.format(
"service:jmx:rmi://%s:%d/jndi/rmi://%s:%d/jmxrmi",
jmxHost, jmxPort, jmxHost, jmxPort
);
connectorServer.setServiceUrl(serviceUrl);
// 配置JMX远程环境
Map<String, Object> environment = new HashMap<>();
if (authenticationEnabled) {
environment.put(JMXConnectorServer.AUTHENTICATOR,
createJmxAuthenticator());
}
if (sslEnabled) {
environment.putAll(configureSsl());
}
connectorServer.setEnvironment(environment);
return connectorServer;
}
private JMXAuthenticator createJmxAuthenticator() {
return authentication -> {
String[] credentials = (String[]) authentication.getCredentials();
String username = credentials[0];
String password = credentials[1];
// 验证用户名密码
if (!isValidJmxUser(username, password)) {
throw new SecurityException("Invalid JMX credentials");
}
// 返回授权主体
return new JMXPrincipal(username);
};
}
private boolean isValidJmxUser(String username, String password) {
// 从配置文件或数据库验证用户
// 简化实现:硬编码验证
Map<String, String> jmxUsers = Map.of(
"jmxadmin", "admin123",
"jmxmonitor", "monitor123"
);
return jmxUsers.containsKey(username) &&
jmxUsers.get(username).equals(password);
}
private Map<String, ?> configureSsl() {
Map<String, Object> sslConfig = new HashMap<>();
try {
// 配置SSL上下文
SSLContext sslContext = SSLContext.getInstance("TLS");
// 加载密钥库和信任库
KeyManagerFactory kmf = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
// 从配置文件读取
String keyStorePath = System.getProperty("javax.net.ssl.keyStore");
String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword");
if (keyStorePath != null) {
KeyStore keyStore = KeyStore.getInstance("JKS");
try (FileInputStream fis = new FileInputStream(keyStorePath)) {
keyStore.load(fis, keyStorePassword.toCharArray());
}
kmf.init(keyStore, keyStorePassword.toCharArray());
tmf.init(keyStore);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
sslConfig.put("com.sun.management.jmxremote.ssl", true);
sslConfig.put("com.sun.management.jmxremote.ssl.need.client.auth", false);
sslConfig.put("javax.net.ssl.SSLContext", sslContext);
}
} catch (Exception e) {
log.error("Failed to configure SSL for JMX", e);
}
return sslConfig;
}
}
/**
* JMX MBean接口定义
*/
// 健康端点MBean接口
public interface HealthEndpointMBeanMBean {
String getHealthStatus();
Map<String, Object> getHealthDetails();
boolean isDatabaseHealthy();
boolean isRedisHealthy();
void refreshHealth();
}
// 指标端点MBean接口
public interface MetricsEndpointMBeanMBean {
long getJvmMemoryUsed();
long getJvmMemoryMax();
double getSystemCpuUsage();
long getHttpRequestsTotal();
double getHttpRequestErrorRate();
Map<String, Double> getCustomMetrics();
void resetMetric(String metricName);
}
// 数据源MBean接口
public interface TomcatDataSourceMBeanMBean {
int getActiveConnections();
int getIdleConnections();
int getMaxActiveConnections();
int getWaitCount();
void resetStat();
String getConnectionPoolConfiguration();
}
public interface HikariDataSourceMBeanMBean {
int getActiveConnections();
int getIdleConnections();
int getTotalConnections();
int getThreadsAwaitingConnection();
long getConnectionTimeout();
String getPoolConfiguration();
}
/**
* 动态MBean注册与注销
*/
public void registerDynamicMBean(String name, Object mbean) throws Exception {
ObjectName objectName = new ObjectName(MBEAN_DOMAIN + ":type=Dynamic,name=" + name);
if (mBeanServer.isRegistered(objectName)) {
log.warn("MBean already registered: {}", objectName);
return;
}
mBeanServer.registerMBean(mbean, objectName);
registeredMBeans.put("dynamic." + name, objectName);
log.info("Registered dynamic MBean: {}", objectName);
}
public void unregisterMBean(String key) throws Exception {
ObjectName objectName = registeredMBeans.get(key);
if (objectName != null && mBeanServer.isRegistered(objectName)) {
mBeanServer.unregisterMBean(objectName);
registeredMBeans.remove(key);
log.info("Unregistered MBean: {}", objectName);
}
}
/**
* JMX操作权限控制
*/
@Component
public class JmxOperationSecurity implements MBeanRegistration {
@Override
public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
// 注册前检查权限
if (!hasPermissionToRegister(name)) {
throw new SecurityException("No permission to register MBean: " + name);
}
return name;
}
@Override
public void postRegister(Boolean registrationDone) {
if (registrationDone) {
log.info("MBean registered successfully");
}
}
@Override
public void preDeregister() throws Exception {
// 注销前检查权限
if (!hasPermissionToUnregister()) {
throw new SecurityException("No permission to unregister MBean");
}
}
@Override
public void postDeregister() {
log.info("MBean unregistered");
}
private boolean hasPermissionToRegister(ObjectName name) {
// 检查当前上下文是否有权限注册MBean
SecurityManager security = System.getSecurityManager();
if (security != null) {
try {
security.checkPermission(new MBeanPermission(
name.getCanonicalName(),
"registerMBean"
));
return true;
} catch (SecurityException e) {
return false;
}
}
return true; // 没有SecurityManager,允许所有
}
private boolean hasPermissionToUnregister() {
// 类似实现...
return true;
}
}
}
2.3 JMX监控指标收集器
java
复制
下载
// JMX指标收集与导出器
@Component
public class JmxMetricsCollector {
@Autowired
private MBeanServer mBeanServer;
@Autowired
private MeterRegistry meterRegistry;
private final Map<String, Gauge> registeredGauges = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
@PostConstruct
public void init() {
// 定期收集JMX指标
scheduler.scheduleAtFixedRate(this::collectJmxMetrics, 0, 30, TimeUnit.SECONDS);
// 动态发现和注册MBean
scheduler.scheduleAtFixedRate(this::discoverNewMBeans, 0, 60, TimeUnit.SECONDS);
}
/**
* 收集JMX指标
*/
public void collectJmxMetrics() {
try {
// 1. 收集JVM内存指标
collectJvmMemoryMetrics();
// 2. 收集GC指标
collectGarbageCollectionMetrics();
// 3. 收集线程指标
collectThreadMetrics();
// 4. 收集类加载指标
collectClassLoadingMetrics();
// 5. 收集操作系统指标
collectOperatingSystemMetrics();
// 6. 收集自定义MBean指标
collectCustomMBeanMetrics();
} catch (Exception e) {
log.error("Failed to collect JMX metrics", e);
}
}
/**
* 收集JVM内存指标
*/
private void collectJvmMemoryMetrics() {
try {
ObjectName memoryMBean = new ObjectName("java.lang:type=Memory");
// 堆内存使用
CompositeData heapUsage = (CompositeData) mBeanServer.getAttribute(
memoryMBean, "HeapMemoryUsage");
long heapUsed = (Long) heapUsage.get("used");
long heapCommitted = (Long) heapUsage.get("committed");
long heapMax = (Long) heapUsage.get("max");
// 注册到Micrometer
registerGauge("jvm.memory.used", heapUsed, "area", "heap");
registerGauge("jvm.memory.committed", heapCommitted, "area", "heap");
if (heapMax != -1) {
registerGauge("jvm.memory.max", heapMax, "area", "heap");
}
// 非堆内存
CompositeData nonHeapUsage = (CompositeData) mBeanServer.getAttribute(
memoryMBean, "NonHeapMemoryUsage");
long nonHeapUsed = (Long) nonHeapUsage.get("used");
long nonHeapCommitted = (Long) nonHeapUsage.get("committed");
registerGauge("jvm.memory.used", nonHeapUsed, "area", "nonheap");
registerGauge("jvm.memory.committed", nonHeapCommitted, "area", "nonheap");
} catch (Exception e) {
log.warn("Failed to collect JVM memory metrics", e);
}
}
/**
* 收集垃圾回收指标
*/
private void collectGarbageCollectionMetrics() {
try {
Set<ObjectName> gcMBeans = mBeanServer.queryNames(
new ObjectName("java.lang:type=GarbageCollector,*"), null);
for (ObjectName gcMBean : gcMBeans) {
String gcName = (String) mBeanServer.getAttribute(gcMBean, "Name");
// 收集次数和时间
Long collectionCount = (Long) mBeanServer.getAttribute(gcMBean, "CollectionCount");
Long collectionTime = (Long) mBeanServer.getAttribute(gcMBean, "CollectionTime");
// 注册计数器
Counter.builder("jvm.gc.collections")
.tag("gc", gcName)
.register(meterRegistry)
.increment(collectionCount);
// 注册计时器
Timer.builder("jvm.gc.pause")
.tag("gc", gcName)
.register(meterRegistry)
.record(collectionTime, TimeUnit.MILLISECONDS);
}
} catch (Exception e) {
log.warn("Failed to collect GC metrics", e);
}
}
/**
* 收集线程指标
*/
private void collectThreadMetrics() {
try {
ObjectName threadMBean = new ObjectName("java.lang:type=Threading");
Integer threadCount = (Integer) mBeanServer.getAttribute(threadMBean, "ThreadCount");
Integer peakThreadCount = (Integer) mBeanServer.getAttribute(threadMBean, "PeakThreadCount");
Long totalStartedThreadCount = (Long) mBeanServer.getAttribute(threadMBean, "TotalStartedThreadCount");
registerGauge("jvm.threads.live", threadCount);
registerGauge("jvm.threads.peak", peakThreadCount);
registerGauge("jvm.threads.started", totalStartedThreadCount);
// 获取死锁线程
long[] deadlockedThreads = (long[]) mBeanServer.getAttribute(threadMBean, "ThreadDeadlock");
if (deadlockedThreads != null) {
registerGauge("jvm.threads.deadlock", (long) deadlockedThreads.length);
}
} catch (Exception e) {
log.warn("Failed to collect thread metrics", e);
}
}
/**
* 发现新的MBean
*/
private void discoverNewMBeans() {
try {
// 查询所有MBean
Set<ObjectName> allMBeans = mBeanServer.queryNames(null, null);
for (ObjectName mbeanName : allMBeans) {
String domain = mbeanName.getDomain();
// 只处理我们感兴趣的域
if (domain.startsWith("com.example") ||
domain.equals("java.lang") ||
domain.equals("Tomcat") ||
domain.equals("Spring")) {
// 检查是否已经注册
if (!isMBeanRegistered(mbeanName)) {
// 分析MBean属性并注册相应指标
analyzeAndRegisterMBean(mbeanName);
}
}
}
} catch (Exception e) {
log.error("Failed to discover new MBeans", e);
}
}
/**
* 分析并注册MBean指标
*/
private void analyzeAndRegisterMBean(ObjectName mbeanName) throws Exception {
MBeanInfo mbeanInfo = mBeanServer.getMBeanInfo(mbeanName);
MBeanAttributeInfo[] attributes = mbeanInfo.getAttributes();
for (MBeanAttributeInfo attribute : attributes) {
// 只处理可读的数值属性
if (attribute.isReadable() && isNumericType(attribute.getType())) {
String metricName = buildMetricName(mbeanName, attribute.getName());
// 创建动态Gauge
Gauge gauge = Gauge.builder(metricName, () -> {
try {
Object value = mBeanServer.getAttribute(mbeanName, attribute.getName());
return convertToDouble(value);
} catch (Exception e) {
log.warn("Failed to read attribute: {}", attribute.getName(), e);
return Double.NaN;
}
})
.tags(buildTags(mbeanName))
.description(attribute.getDescription())
.register(meterRegistry);
registeredGauges.put(metricName, gauge);
log.debug("Registered dynamic gauge for MBean: {} -> {}",
mbeanName, attribute.getName());
}
}
}
/**
* 构建指标名称
*/
private String buildMetricName(ObjectName mbeanName, String attributeName) {
String domain = mbeanName.getDomain().replace('.', '_');
String type = mbeanName.getKeyProperty("type");
String name = mbeanName.getKeyProperty("name");
StringBuilder metricName = new StringBuilder("jmx.");
metricName.append(domain);
if (type != null) {
metricName.append(".").append(type);
}
if (name != null) {
metricName.append(".").append(name);
}
metricName.append(".").append(attributeName.toLowerCase());
return metricName.toString();
}
/**
* 构建标签
*/
private Tags buildTags(ObjectName mbeanName) {
List<Tag> tags = new ArrayList<>();
// 添加MBean的所有键值对作为标签
for (Map.Entry<String, String> entry : mbeanName.getKeyPropertyList().entrySet()) {
tags.add(Tag.of(entry.getKey(), entry.getValue()));
}
return Tags.of(tags);
}
/**
* 判断是否为数值类型
*/
private boolean isNumericType(String type) {
return type.equals("int") || type.equals("long") || type.equals("double") ||
type.equals("float") || type.equals("java.lang.Integer") ||
type.equals("java.lang.Long") || type.equals("java.lang.Double") ||
type.equals("java.lang.Float");
}
/**
* 转换为double
*/
private double convertToDouble(Object value) {
if (value == null) {
return Double.NaN;
}
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
try {
return Double.parseDouble(value.toString());
} catch (NumberFormatException e) {
return Double.NaN;
}
}
/**
* 注册Gauge
*/
private void registerGauge(String name, Number value, String... tags) {
String gaugeKey = name + Arrays.toString(tags);
Gauge gauge = registeredGauges.get(gaugeKey);
if (gauge == null) {
gauge = Gauge.builder(name, () -> value.doubleValue())
.tags(tags)
.register(meterRegistry);
registeredGauges.put(gaugeKey, gauge);
}
}
}
2.4 性能优化与缓存策略
java
复制
下载
// JMX性能优化与缓存
@Component
public class JmxPerformanceOptimizer {
private final Cache<MBeanQuery, Object> mbeanCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.SECONDS)
.recordStats()
.build();
private final Cache<String, MBeanAttributeMetadata> attributeMetadataCache =
Caffeine.newBuilder()
.maximumSize(500)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
/**
* 带缓存的MBean属性读取
*/
public Object getCachedAttribute(ObjectName objectName, String attribute)
throws Exception {
MBeanQuery query = new MBeanQuery(objectName, attribute);
return mbeanCache.get(query, key -> {
try {
return readAttributeWithRetry(key.objectName, key.attribute);
} catch (Exception e) {
throw new RuntimeException("Failed to read attribute", e);
}
});
}
/**
* 带重试的属性读取
*/
private Object readAttributeWithRetry(ObjectName objectName, String attribute)
throws Exception {
int maxRetries = 3;
int retryDelay = 100; // 毫秒
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return mBeanServer.getAttribute(objectName, attribute);
} catch (InstanceNotFoundException e) {
// MBean不存在,不需要重试
throw e;
} catch (Exception e) {
if (attempt == maxRetries) {
throw e;
}
log.warn("Failed to read attribute {}.{}, attempt {}/{}",
objectName, attribute, attempt, maxRetries, e);
Thread.sleep(retryDelay * attempt);
}
}
throw new IllegalStateException("Should not reach here");
}
/**
* 批量读取MBean属性(优化性能)
*/
public Map<String, Object> getAttributesBatch(ObjectName objectName,
String[] attributes) {
Map<String, Object> results = new HashMap<>();
List<String> uncachedAttributes = new ArrayList<>();
// 检查缓存
for (String attribute : attributes) {
MBeanQuery query = new MBeanQuery(objectName, attribute);
Object cachedValue = mbeanCache.getIfPresent(query);
if (cachedValue != null) {
results.put(attribute, cachedValue);
} else {
uncachedAttributes.add(attribute);
}
}
// 批量读取未缓存的属性
if (!uncachedAttributes.isEmpty()) {
try {
AttributeList attributeList = mBeanServer.getAttributes(
objectName, uncachedAttributes.toArray(new String[0]));
for (Attribute attr : attributeList.asList()) {
results.put(attr.getName(), attr.getValue());
// 更新缓存
MBeanQuery query = new MBeanQuery(objectName, attr.getName());
mbeanCache.put(query, attr.getValue());
}
} catch (Exception e) {
log.error("Failed to batch read attributes", e);
}
}
return results;
}
/**
* MBean查询缓存键
*/
static class MBeanQuery {
final ObjectName objectName;
final String attribute;
MBeanQuery(ObjectName objectName, String attribute) {
this.objectName = objectName;
this.attribute = attribute;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MBeanQuery that = (MBeanQuery) o;
return objectName.equals(that.objectName) &&
attribute.equals(that.attribute);
}
@Override
public int hashCode() {
return Objects.hash(objectName, attribute);
}
}
/**
* MBean属性元数据
*/
static class MBeanAttributeMetadata {
String name;
String type;
String description;
boolean readable;
boolean writable;
boolean isIs; // 是否为isXXX格式的布尔属性
// 性能统计
long lastAccessTime;
long accessCount;
double averageReadTime;
}
/**
* JMX连接池(用于高并发场景)
*/
@Component
public static class JmxConnectionPool {
private final BlockingQueue<JMXConnector> connectionPool;
private final String serviceUrl;
private final Map<String, String> environment;
private final int maxConnections = 10;
private final int minConnections = 2;
public JmxConnectionPool(@Value("${jmx.service.url}") String serviceUrl) {
this.serviceUrl = serviceUrl;
this.environment = new HashMap<>();
this.connectionPool = new ArrayBlockingQueue<>(maxConnections);
initializePool();
}
private void initializePool() {
for (int i = 0; i < minConnections; i++) {
try {
connectionPool.add(createConnection());
} catch (Exception e) {
log.error("Failed to create JMX connection", e);
}
}
}
public JMXConnector borrowConnection() throws Exception {
JMXConnector connector = connectionPool.poll();
if (connector == null) {
// 池中没有可用连接,创建新连接
if (connectionPool.size() < maxConnections) {
connector = createConnection();
} else {
// 等待连接释放
connector = connectionPool.take();
}
}
// 检查连接是否有效
if (!isConnectionValid(connector)) {
connector.close();
connector = createConnection();
}
return connector;
}
public void returnConnection(JMXConnector connector) {
if (connector != null && isConnectionValid(connector)) {
connectionPool.offer(connector);
} else {
try {
if (connector != null) {
connector.close();
}
} catch (IOException e) {
log.warn("Failed to close JMX connection", e);
}
}
}
private JMXConnector createConnection() throws IOException {
JMXServiceURL url = new JMXServiceURL(serviceUrl);
return JMXConnectorFactory.connect(url, environment);
}
private boolean isConnectionValid(JMXConnector connector) {
try {
connector.getMBeanServerConnection().getMBeanCount();
return true;
} catch (Exception e) {
return false;
}
}
public void closeAllConnections() {
while (!connectionPool.isEmpty()) {
try {
JMXConnector connector = connectionPool.take();
connector.close();
} catch (Exception e) {
log.warn("Failed to close JMX connection", e);
}
}
}
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
三、整合配置与最佳实践
3.1 完整的application.yml配置
yaml
复制
下载
# application-actuator-jmx.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
exclude: env,beans,configprops,loggers
base-path: /manage
cors:
allowed-origins: "https://trusted-domain.com"
allowed-methods: GET,HEAD
path-mapping:
health: healthcheck
metrics: performance
# 端点安全配置
access:
health: PUBLIC
info: AUTHENTICATED
metrics: MONITOR_ROLE
env: ADMIN_ROLE
loggers: ADMIN_ROLE
jmx:
exposure:
include: health,metrics,info
exclude: env
domain: com.example.app
unique-names: true
# 端点敏感度配置
sensitive:
health: false
info: false
metrics: true
env: true
loggers: true
beans: true
configprops: true
endpoint:
health:
show-details: when_authorized
show-components: when_authorized
roles: MONITOR,ADMIN
probes:
liveness:
enabled: true
path: /manage/health/live
readiness:
enabled: true
path: /manage/health/ready
metrics:
enabled: true
export:
prometheus:
enabled: true
step: 1m
distribution:
percentiles-histogram:
http.server.requests: true
percentiles: 0.5,0.75,0.95,0.99
sla:
http.server.requests: 100ms,200ms,500ms,1s
# JMX配置
jmx:
enabled: true
host: localhost
port: 1099
ssl:
enabled: false
authentication:
enabled: true
users:
- username: jmxadmin
password: ${JMX_ADMIN_PASSWORD:admin123}
roles: ADMIN
- username: jmxmonitor
password: ${JMX_MONITOR_PASSWORD:monitor123}
roles: MONITOR
# 安全配置
security:
actuator:
enabled: true
ips: 192.168.1.0/24,10.0.0.0/8
header:
name: X-Actuator-Token
value: ${ACTUATOR_TOKEN:}
enabled: true
roles:
admin: ACTUATOR_ADMIN
monitor: ACTUATOR_MONITOR
user: ACTUATOR_USER
# 用户配置(内存存储示例)
user:
name: ${ACTUATOR_USERNAME:actuator}
password: ${ACTUATOR_PASSWORD:}
roles: MONITOR,USER
# 日志配置
logging:
level:
org.springframework.boot.actuate: INFO
com.example.actuator: DEBUG
3.2 最佳实践总结
-
安全最佳实践:
-
始终在生产环境启用Actuator安全
-
使用最小权限原则配置端点访问
-
定期轮换认证凭据
-
监控和审计所有Actuator访问
-
-
JMX最佳实践:
-
在生产环境启用JMX认证
-
使用防火墙限制JMX端口访问
-
定期监控JMX连接数
-
考虑使用JMX over SSH隧道
-
-
性能最佳实践:
-
为高频率访问的端点启用缓存
-
批量读取JMX属性减少RPC调用
-
使用连接池管理JMX连接
-
监控JMX操作响应时间
-
-
监控最佳实践:
-
集成Actuator指标到现有监控系统
-
设置合理的告警阈值
-
定期review端点暴露配置
-
实现自动化健康检查
-
通过这种全面的安全过滤和JMX导出实现,可以在保障系统安全的前提下,提供强大的监控和管理能力。