简介
之前使用JMX管理也没有太在意,最近重新看到JMX配置,发现这也是一种比较灵活的配置管理方式。主要是有很多现成的工具可以用。
在看JMX之前我们先回顾一下之前的配置方式。直接写死在程序里就不说了,这个相当于没有配置,可能只是让配置集中一点方便管理而已。每次修改之后都得重新打包,直接可以放弃。
下面我们直接看使用配置文件的方式。
配置文件配置
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直接放在类所在包下面就可以了。这个版本的配置有一个问题就是,修改了配置文件必须重启应用。重启应用也是个比较麻烦的事情。
所有我们修改一下来个动态配置版的,可以使用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中也好了,真是奇怪。
不过解决这个问题的过程中也发现一些有趣的事情,比如说Paths,Path,Files,FileTime等类。之前处理文件一般都是使用apach的common.io,今天发现jdk1.7开始的Paths,Path,Files,FileTime这些类也可以很方便的处理遍历,复制等操作了。有兴趣可以尝试一下。
JMX配置
这里我们使用standard MBean,standard MBean它能管理的资源(包括属性,方法,时间)必须定义在接口中,然后MBean必须实现这个接口。它的命名也必须遵循一定的规范,例如我们的MBean为Congfig,则接口必须为CongfigMBean。
先上代码:
standard MBean接口
public interface ConfigMBean {
String getName();
void setName(String name);
int getPort();
void setPort(int port);
String printName();
int printPort();
}
standard MBean
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方式的限制也比较多,开发的成本也会大一些。