以前使用JMX管理也没有太在乎,最近从新看到JMX配置,发现这也是一种比较灵活的配置管理方式。主要是有不少现成的工具能够用。java
在看JMX以前咱们先回顾一下以前的配置方式。直接写死在程序里就不说了,这个至关于没有配置,可能只是让配置集中一点方便管理而已。每次修改以后都得从新打包,直接能够放弃。服务器
下面咱们直接看使用配置文件的方式。dom
import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.concurrent.TimeUnit; public class ConfigUtil { private static Properties config; static { config = new Properties(); InputStream is = ConfigUtil.class.getResourceAsStream("config.properties"); try { config.load(is); } catch (IOException e) { e.printStackTrace(); } } public static String getConfig(String key){ return config.getProperty(key); } public static void main(String[] args) { for(int i = 0;i<1000;i++) { System.out.println(ConfigUtil.getConfig("port")); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } } }
config.properties直接放在类所在包下面就能够了。这个版本的配置有一个问题就是,修改了配置文件必须重启应用。重启应用也是个比较麻烦的事情。ide
全部咱们修改一下来个动态配置版的,可使用apach的common.io中的文件监控。不过我以为没有必要,直接简单判断一下最后修改时间就能够了。直接上代码。工具
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.URL; import java.util.Properties; import java.util.concurrent.TimeUnit; public class ConfigPromoteUtil { public static final String FILE_NAME = "config.properties"; private static Properties config = new Properties(); private static long lastModify; private static File file; static { URL url = ConfigPromoteUtil.class.getResource(FILE_NAME); String path = url.getFile(); file = new File(path); loadConfig(); } private static void loadConfig(){ lastModify = file.lastModified(); try { FileInputStream fis = new FileInputStream(file); config.load(fis); } catch (IOException e) { e.printStackTrace(); } } /** * 配置文件是否被修改 * @return true 被修改 不然 false */ private static boolean isModify(){ long lastModified = file.lastModified(); return lastModify != lastModified; } // private static boolean isModify() throws IOException { // URL url = ConfigPromoteUtil.class.getResource(FILE_NAME); // String path = url.getFile(); // System.out.println(path); // Path p = Paths.get(path.substring(1)); // FileTime ft = Files.getLastModifiedTime(p); // long lastModified = ft.toMillis(); // return lastModify != lastModified; // } public static String getConfig(String key) throws IOException { if(isModify()) loadConfig(); return config.getProperty(key); } public static void main(String[] args) throws IOException { for(int i = 0;i<1000;i++) { System.out.println(ConfigPromoteUtil.getConfig("port")); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } } }
逻辑仍是蛮简单的,初始化的时候就先加载配置文件,记录最后修改时间,而后每一次获取配置的时候,都去检查文件的最后修改时间有没有变化,若是变了就从新加载配置文件,并修改最后修改时间。测试
刚刚开始的时候再idea中发现获取的配置文件最后修改时间怎么都不对,尝试了不少方法都没有效果,后来直接用命令行运行了一下,既然好了,而后在idea中也好了,真是奇怪。this
不过解决这个问题的过程当中也发现一些有趣的事情,好比说Paths,Path,Files,FileTime等类。以前处理文件通常都是使用apach的common.io,今天发现jdk1.7开始的Paths,Path,Files,FileTime这些类也能够很方便的处理遍历,复制等操做了。有兴趣能够尝试一下。url
这里咱们使用standard MBean,standard MBean它能管理的资源(包括属性,方法,时间)必须定义在接口中,而后MBean必须实现这个接口。它的命名也必须遵循必定的规范,例如咱们的MBean为Congfig,则接口必须为CongfigMBean。idea
先上代码:.net
public interface ConfigMBean { String getName(); void setName(String name); int getPort(); void setPort(int port); String printName(); int printPort(); }
public class Config implements ConfigMBean { private String name; private int port; public String printName(){ System.out.println(name); return name; } public int printPort(){ System.out.println(port); return port; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } @Override public int getPort() { return port; } @Override public void setPort(int port) { this.port = port; } }
import javax.management.*; import javax.management.remote.*; import java.io.IOException; import java.lang.management.ManagementFactory; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.util.Scanner; import java.util.concurrent.TimeUnit; public class ConfigAgent { public static void main(String[] args) throws Exception { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); ObjectName on = null; try { on = new ObjectName("jmxBean:name=config"); } catch (MalformedObjectNameException e) { e.printStackTrace(); } try { server.registerMBean(new Config(), on); } catch (InstanceAlreadyExistsException e) { e.printStackTrace(); } catch (MBeanRegistrationException e) { e.printStackTrace(); } catch (NotCompliantMBeanException e) { e.printStackTrace(); } try { LocateRegistry.createRegistry(9999); JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi"); JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server); jcs.start(); } catch (RemoteException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } final Config config = new Config(); for(int i=0;i<1000;i++){ System.out.println(config.getName()); System.out.println(config.getPort()); config.printName(); config.printPort(); print(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } final Scanner scanner = new Scanner(System.in); scanner.nextLine(); } public static void print() throws Exception { JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi"); JMXConnector jmxc = JMXConnectorFactory.connect(url,null); MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); ObjectName mbeanName = new ObjectName("jmxBean:name=config"); // String[] domains = mbsc.getDomains(); // mbsc.getMBeanCount() // mbsc.setAttribute(mbeanName, new Attribute("Name","tim")); // mbsc.setAttribute(mbeanName, new Attribute("Port","3306")); // int port = (Integer) mbsc.getAttribute(mbeanName, "Port"); // String name = (String)mbsc.getAttribute(mbeanName, "Name"); // System.out.println("port=" + port + ";name=" + name); ConfigMBean proxy = MBeanServerInvocationHandler.newProxyInstance(mbsc, mbeanName, ConfigMBean.class, false); proxy.printPort(); proxy.printName(); System.out.println(proxy.getName()); System.out.println(proxy.getPort()); } }
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
获取一个MBean管理服务器的实例。
ObjectName on = new ObjectName("jmxBean:name=config");
给MBean一个标识,方便外部识别和访问。
server.registerMBean(new Config(), on);
在MBean服务器上注册一个Config MBean。
到这里就算是注册MBean完了,而且可使用JConsole这样的工具链接查看修改了。可是若是要提供一些外部的Adapter和远程的RMI调用就还须要一些工做。
LocateRegistry.createRegistry(9999); JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi"); JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url,null, server); jcs.start();
注册一个JMX服务,而且使用的是rmi协议。
从上面的测试中能够看到直接在new一个Config是不行的,若是要统一配置,获取外部能够修改的Config仍是得JMXServiceURL来访问。
public static void print() throws Exception { JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi"); JMXConnector jmxc = JMXConnectorFactory.connect(url,null); MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); ObjectName mbeanName = new ObjectName("jmxBean:name=config"); // String[] domains = mbsc.getDomains(); // mbsc.getMBeanCount() // mbsc.setAttribute(mbeanName, new Attribute("Name","tim")); // mbsc.setAttribute(mbeanName, new Attribute("Port","3306")); // int port = (Integer) mbsc.getAttribute(mbeanName, "Port"); // String name = (String)mbsc.getAttribute(mbeanName, "Name"); // System.out.println("port=" + port + ";name=" + name); ConfigMBean proxy = MBeanServerInvocationHandler.newProxyInstance(mbsc, mbeanName, ConfigMBean.class, false); proxy.printPort(); proxy.printName(); System.out.println(proxy.getName()); System.out.println(proxy.getPort()); }
咱们看print方法,仍是得经过JMXServiceURL,链接到MBean服务暴露的接口,经过MBeanServerInvocationHandler获取到代理的Config MBean。
这样咱们能够经过JConsole等工具链接上MBean服务器,修改了配置,在程序中也能获取到。以下图:
上图是在JConsole中修改Config配置,注意属性是Config中的属性,操做是定义在ConfigMBean中的非getter,setter方法。
对于简单的配置直接使用动态配置文件就能够了,若是配置管理比较多久能够考虑JMX的方式。JMX方式的限制也比较多,开发的成本也会大一些。