• JMX概念及实际开发应用【实现IP黑名单】


    JMX技术介绍及使用

    在这里插入图片描述

    1 JMX概念

    JMX:Java Management Extensions技术是Java SE平台的标准功能,提供了一种简单的、标准的监控和管理资源的方式。主要用于监控和管理Java应用程序运行状态、设备和资源信息、Java虚拟机运行情况等信息。JMX可以是动态的,所以可以在资源创建、安装、实现时进行动态监控和管理,JDK自带的jconsole就是使用JMX技术实现的监控工具。

    使用JMX技术时,需要定义一个被称为MBean或MXBean的Java对象来表示要管理指定的资源,然后可以把资源信息注册到MBean Server对外提供服务。MBean Server充当了对外提供服务和内存管理MBean资源的代理功能。

    JMX不仅可以用于本地管理,JMX Remote API还为JMX添加了远程功能,使之可以通过网络远程监视和管理应用程序。

    2 JMX作用

    1. 开箱即用。提供了资源管理、服务托管、远程监控等管理基础功能。
    2. 支持远程使用。不仅可以本地使用,还支持远程调用,如Java EE应用等。
    3. 内置JVM监测。可以监控和管理JVM。
    4. JMX架构优秀,其组件化设计可以自由扩展。
    5. JMX技术严格遵守Java现有规范,如JNDI规范

    JNDI:JavaNaming And Directory Interface,Java命名和目录结构
    命名服务,如:域名映射到ip地址的服务
    目录服务,如:电话簿,如果我们要找某个人的电话号码,我们需要从电话簿里找到这个人的名称,然后再看其电话号码

    1. JMX可以自由的与其他管理解决方案集成,可以通过Web服务管理JMX中的资源。

    3 JMX技术架构

    JMX技术架构主要有资源管理(MBean/MXBean)模块、资源代理模块(MBean Server)、远程管理模块(Remote API)组成。
    在这里插入图片描述

    3.1 资源管理MBean

    资源管理在架构中标识为资源探测层(Probe Level),在JMX中,使用MBean或 MXBean来表示一个资源(下面简称MBean),访问和管理资源也都是通过MBean,所以MBean往往包含着资源的属性和操作方法。

    JMX已经对JVM进行了多维度的资源检测,所以可以轻松启动JMX代理来访问内置的JVM资源检测,从而通过JMX技术远程监控和管理JVM。

    以下是JMX对JVM的资源检测类,都可以直接使用。
    在这里插入图片描述

    3.1.1 实际操作

    package com.example.test.bean;
    
    import java.lang.management.*;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class JavaManagementExtensions {
        public static void main(String[] args) {
            //操作系统信息
            OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
            String osName = operatingSystemMXBean.getName();
            String osVersion = operatingSystemMXBean.getVersion();
            int processors = operatingSystemMXBean.getAvailableProcessors();
            System.out.println(String.format("操作系统:%s,版本:%s,处理器:%d个", osName, osVersion, processors));
    
            //编译系统信息
            CompilationMXBean compilationMXBean = ManagementFactory.getCompilationMXBean();
            String compilationMXBeanName = compilationMXBean.getName();
            System.out.println("编译系统:" + compilationMXBeanName);
    
            //内存信息
            MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
            MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
            long max = heapMemoryUsage.getMax();
            long used = heapMemoryUsage.getUsed();
            System.out.println(String.format("使用内存:%dMB/%dMB", used / 1024 / 1024, max / 1024 / 1024));
    
            //垃圾收集器信息
            List<GarbageCollectorMXBean> gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
            String gcNames = gcMXBeans.stream()
                    .map(MemoryManagerMXBean::getName)
                    .collect(Collectors.joining(","));
            System.out.println("垃圾收集器:" + gcNames);
        }
    }
    
    
    • 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

    3.1.2 结果

    操作系统:Windows 10,版本:10.0,处理器:8个
    编译系统:HotSpot 64-Bit Tiered Compilers
    使用内存:6MB/3604MB
    垃圾收集器:PS Scavenge,PS MarkSweep
    
    • 1
    • 2
    • 3
    • 4

    3.2 资源代理 MBean Server

    资源代理MBean Server是MBean资源的代理,通过MBean Server可以让MBean资源用于远程管理,MBean资源和MBean Server通常是在同一个JVM中,但不是必须的。

    想要MBean Server管理MBean资源,首先要把资源注册到MBean Server,任何符合JMX的MBean资源都可以进行注册,最后MBean Server会暴露一个远程通信接口对外提供服务。

    合规的MBean -> 注册到MBServer -> MBServer会暴露接口提供服务
    
    • 1

    3.3 JMX远程管理

    可以通过网络协议访问JMX API,如HTTP协议,SNMP(网络管理协议)、RMI远程方法调用协议等,JMX技术默认实现了RMI远程调用协议。

    因为资源管理MBean的充分解耦,所以我们可以轻松的把资源管理功能扩展到其他协议,如:通过HTTP在网页端进行管理。

    4 JMX实际应用

    演示自定义资源MBean,模拟一个内存资源MBean,最后对它进行进程管理

    4.1 自定义内存资源管理Bean(普通Java工程方式)

    4.1.1 编写资源管理MBean

    MBean的编写必须遵守JMX的设计规范,MBean类似于一个特殊的Java Bean,它需要一个接口和一个实现类。MBean资源接口总是以MBean或者MXBean结尾,实现类则是要以接口去掉MBean或MXBean之后的名字来命名。

    命名规范:

    例如:
    MBean资源接口:MyMemoryMBean/MyMemoryMXBean
    实现类:MyMemory
    
    • 1
    • 2
    • 3

    自定义编写MyMemoryMXBean:

    package com.example.test.bean;
    
    //定义MBean资源接口[如果资源接口中含有自定义实体类的引用,则需要以MXBean结尾,MyMemoryMXBean]
    public interface MyMemoryMBean {
    
        long getTotal();
    
        void setTotal(long total);
    
        long getUsed();
    
        void setUsed(long used);
    
        String doMemoryInfo();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    4.1.2 编写实现类MyMemory
    package com.example.test.bean;
    
    public class MyMemory implements MyMemoryMBean{
    
        private long total;
        private long used;
    
        @Override
        public long getTotal() {
            return total;
        }
    
        @Override
        public void setTotal(long total) {
            this.total = total;
        }
    
        @Override
        public long getUsed() {
            return used;
        }
    
        @Override
        public void setUsed(long used) {
            this.used = used;
        }
    
        @Override
        public String doMemoryInfo() {
            return String.format("使用内存:%dMB/%dMB", used, total);
        }
    }
    
    
    • 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

    注意:
    ①这个例子在MyMemory.java中只有两个long基本类型属性,所以接口是以MBean结尾。如果资源实现类中的属性是自定义实体类的引用,那么接口就需要以MXBean结尾。
    ②MyMemory与MyMemoryMBea需要在同一个包下,否则的话,需要额外实现其他接口

    这样就完成了线程数量资源MBean的创建,其中total和used是资源属性,doMemoryInfo是资源操作方法。

    4.1.3 注册资源到MBean Server

    通过上面的JMX架构图,我们可以知道MBean资源需要注册到MBean Server进行代理才可以暴露给外部进行调用。所以我们想要通过远程管理我们自定义的MyMemory资源,需要先进行资源代理。

    package com.example.test.demo;
    
    import com.example.test.bean.MyMemory;
    
    import javax.management.*;
    import java.lang.management.ManagementFactory;
    
    public class MyMemoryManagement {
    
        public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException, InterruptedException {
            //获取MBean Server
            MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
            //创建【实现类】服务
            MyMemory myMemory = new MyMemory();
            myMemory.setTotal(100L);
            myMemory.setUsed(20L);
            //注册服务到MBean Server
            //com.example.test.bean:type=myMemory [包名:type=服务名]
            ObjectName objectName = new ObjectName("com.example.test.bean:type=myMemory");
            platformMBeanServer.registerMBean(myMemory, objectName);
    
            while(true){
                Thread.sleep(3000);
                System.out.println(myMemory.doMemoryInfo());
            }
        }
    }
    
    • 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

    结果:

    使用内存:20MB/100MB
    使用内存:20MB/100MB
    
    • 1
    • 2

    在这里插入图片描述

    4.1.4 远程管理jconsole
    • jconsole是Java自带的基于JMX技术的监控管理工具,如果已经配置了JDK环境变量,可以直接在cmd窗口输入jconsole命令启动。
      在这里插入图片描述

    • 启动jconsole后会列出当前机器上的Java进程,我们选择自己想要监控的Java进程监控即可,连接后会提示不安全的协议,因为Java程序默认启动是不会配置HTTPS协议。
      在这里插入图片描述

    • 连接成功之后就可以看到多维度的JVM监控信息,这些信息都是通过读取JVM资源MBean信息得到的。

    4.2 通过JMX实现动态IP黑名单【基于SpringBoot】

    传统的应用程序使用JMX,只需要两步:
    1.编写MBean,提供管理接口和监控数据
    2.注册MBean
    在Spring应用程序中,使用JMX只需要一步:
    编写MBean,提供管理接口和监控数据【第二步的注册由Spring自动完成,加上@EnableMBeanExport,告诉Spring自动注册MBean】

    4.2.1 编写MBean【BlacklistBean】
    package com.example.test.demo;
    
    import org.springframework.jmx.export.annotation.ManagedAttribute;
    import org.springframework.jmx.export.annotation.ManagedOperation;
    import org.springframework.jmx.export.annotation.ManagedOperationParameter;
    import org.springframework.jmx.export.annotation.ManagedResource;
    import org.springframework.stereotype.Component;
    
    import java.util.HashSet;
    import java.util.Set;
    
    @Component
    @ManagedResource(objectName = "com.example.test.demo:name=blacklist", description = "Blacklist of IP address")
    public class BlacklistMBean {
        private Set<String> ips = new HashSet<>();
    
        @ManagedAttribute(description = "Get IP address in blacklist")
        public String[] getBlacklist(){
            int size = ips.size();
            String[] ans = new String[size];
            int i = 0;
            for(String str : ips){
                ans[i++] = str;
            }
            return ans;
        }
    
        @ManagedOperation
        @ManagedOperationParameter(name="ip", description = "Target IP address that will be added to blacklist")
        public void addBlacklist(String ip){
            ips.add(ip);
        }
    
        @ManagedOperation
        @ManagedOperationParameter(name="ip", description = "Target IP address that will be removed from blacklist")
        public void removeBlacklist(String ip){
            ips.remove(ip);
        }
    
        public boolean shouldBlock(String ip){
            return ips.contains(ip);
        }
    
    }
    
    
    • 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
    4.2.2 自定义拦截器【拦截黑名单中的ip请求】
    package com.example.test.demo;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * 自定义拦截器
     */
    @Order(1)
    @Component
    public class BlacklistInterceptor implements HandlerInterceptor {
        final Logger logger = LoggerFactory.getLogger(getClass());
    
        @Autowired
        BlacklistMBean blacklistMBean;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String ip = request.getRemoteAddr();
            logger.info("check ip address {}...", ip);
            if(blacklistMBean.shouldBlock(ip)){
                logger.warn("will block ip {} for it is in blacklist...", ip);
                response.sendError(403);
                return false;
            }
            return true;
        }
    }
    
    • 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
    4.2.3 注册拦截器到SpringBoot环境中
    必须加上@Configuration注解,Spring才能统一管理当前拦截器的实例
    addPathPatterns("/api/**")配置拦截路径,
    其中/**表示当前目录以及所有子目录(递归),/*表示当前目录,不包括子目录
    
    • 1
    • 2
    • 3
    package com.example.test.demo;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    @ComponentScan
    @EnableWebMvc
    public class WebConfig implements WebMvcConfigurer {
    
    	//注入自定义拦截器
        @Autowired
        private BlacklistInterceptor blacklistInterceptor;
    
        //添加自定义拦截器【拦截所有请求】
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
    
            registry.addInterceptor(blacklistInterceptor).addPathPatterns("/**");
    
        }
    }
    
    
    • 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
    4.2.4 编写controller及SpringBoot启动类配置

    SpringBoot启动类配置(加上@EnableMBeanExport):

    @SpringBootApplication
    @EnableMBeanExport // 自动注册MBean
    public class TestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(TestApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    controller编写:

    package com.example.test.demo.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class JMXController {
    
        @RequestMapping("/test")
        public String testController(){
            return "jmx test....";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4.3 通过JMX动态改变MXBean的值

    4.3.1 启动jconsole

    启动cmd窗口,输入jconsole,选择本地进程【黑名单进程TestApplication】,并连接(默认是不安全连接,无视即可)

    在这里插入图片描述

    4.3.2 选择MBean,查看MBean,选择自己的应用

    在这里插入图片描述
    此时查看ip黑名单列表,发现没有任何数据
    在这里插入图片描述
    在浏览器上访问地址:

    http://127.0.0.1:8080/test
    
    • 1

    出现结果:

    jmx test....
    
    • 1

    服务器日志打印信息:

    2022-09-02 11:45:03.522  INFO 15084 --- [nio-8080-exec-1] c.e.test.demo.BlacklistInterceptor       : check ip address 127.0.0.1...
    
    • 1
    4.3.3 操作MBean:将127.0.0.1添加进ip黑名单,再测试

    在这里插入图片描述
    在这里插入图片描述

    调用getBlacklist方法,发现127.0.0.1已经添加进去了:
    在这里插入图片描述
    这个时候再访问地址:

    http://127.0.0.1:8080/test
    
    • 1

    发现结果:
    在这里插入图片描述

    服务器日志信息:

    2022-09-02 13:45:30.817  INFO 15084 --- [nio-8080-exec-4] c.e.test.demo.BlacklistInterceptor       : check ip address 127.0.0.1...
    2022-09-02 13:45:30.817  WARN 15084 --- [nio-8080-exec-4] c.e.test.demo.BlacklistInterceptor       : will block ip 127.0.0.1 for it is in blacklist...
    
    • 1
    • 2

    可以发现,我们可以通过jconsole动态修改MBean的属性,动态调用其方法

    使用jconsole直接通过Local Process 连接JVM有个限制,就是jconsole和正在运行的JVM必须在同一台机器。如果要开远程连接,首先需要打开JMX端口。同时,在启动项目时,需要添加以下JVM启动参数:

    • Dcom.sun.management.jmxremote.port=19999
    • Dcom.sun.management.jmxremote.authenticate=false
    • Dcom.sun.management.jmxremote.ssl=false
    第一个参数表示在1999端口监听JMX连接
    第二个和第三个表示无需验证,不使用SSL连接,在开发测试阶段比较方便,生产环境必须指定验证
    方式并启用SSL(安全性)
    
    许多JavaEE服务器,如JBoss的管理后台都是通过JMX提供管理接口,并由Web方式访问,对用户更
    加友好
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    原文链接:
    ①https://www.liaoxuefeng.com/wiki/1252599548343744/1282385687609378
    ②https://www.wdbyte.com/java/jmx.html#_3-jmx-%E7%9A%84%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84

  • 相关阅读:
    2022年如何学会成为一名Android开发
    MacOS安装redis
    java实现一个简单的监听器
    小数第n位【蓝桥杯】
    React中函数式组件与类组件有何不同?
    常看:华为服务器问题记录docker nginx
    Python 全栈系列185 Mongo分表性能测试
    29java容器方法概述(第二级结构)
    【退役之重学Java】关于Spring Cloud 微服务和分布式
    C#对象序列化
  • 原文地址:https://blog.csdn.net/weixin_45565886/article/details/126655040