以前使用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方式的限制也比較多,開發的成本也會大一些。