JNDI简介与SPI实现
JNDI(Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API。命名服务将名称和对象联系起来,使得我们可以用名称访问对象。JNDI类似于JDBC一样,提供了一系列编程接口,用来规范命名服务中对象的创建和管理,通过JNDI可以实现对象的创建与托管,和对象的使用过程完全解耦.比如:在application的底层创建对象,并将对象bind到特定的context中,对象的创建过程或者"查找"方式只有此底层模块支持,外部程序不可见.对于对象的使用者(调用者)只能通过JNDI的方式获取对象,对象使用者无法直接创建对象等.在JDBC/JMS等程序开发时,我们通常将"JDBC/JMS"某些实例(或服务)的提供者交给"容器",这些容器可以为web server或者是spring容器;对于容器内的应用程序,可以简单的通过JNDI的方式来获取服务即可.而无需额外的关注它们创建的过程/托管的方式,甚至不能修改它们.本实例简单的展示了JNDI SPI的实现,模拟一个"配置管理中心",通过web server或者spring容器的方式,向"配置管理中心"提交配置信息,应用程序可以通过JNDI的方式来查找相应的配置等.本实例中包括了:1) ConfigInitialContextFactory.java : 它实现了 javax.naming.spi.InitialContextFactory接口,通过调用者传递的"环境参数"来创建Context查找点.应用程序(通常为客户端)使用.2) ConfigContext.java : 实现了javax.naming.Context接口,它主要负责托管绑定在Context上的所有object,并提供了基于路径的查找方式.3) ConfigObjectFactory.java : 实现了javax.naming.spi.ObjectFactory接口,用于容器(Container)来创建或者获取对象.从JNDI中lookup得到的对象,是否线程安全?答:它和Context以及object的实现有关,如果从Context中每次lookup得到的都是新对象,且此对象不会在多线程环境中使用,这也就不会有线程安全的问题.此外,object如果支持并发操作,它也是线程安全的.不同的JNDI SPI的实现不同,有可能每次lookup出来的对象都是不同的object..不过根据JNDI的规范要求,通过context.bind的对象,然后通过context.lookup,应该是同一个对象.1. Config.java"配置"信息,一个Config对象表示一条配置信息,普通的javabean,它实现了Reference接口.在JNDI Context中绑定的就是Config实例.Java代码

import javax.naming.NamingException;import javax.naming.Reference;import javax.naming.Referenceable;import javax.naming.StringRefAddr;import java.io.Serializable;import java.util.HashSet;import java.util.Set;public class Config implements Referenceable, Serializable {private String name;private String sources;//配置文件中允许配置的"属性"protected static Set<String> properties = new HashSet<String>();static {properties.add("name");properties.add("sources");}protected Config() {}protected Config(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSources() {return sources;}public void setSources(String sources) {this.sources = sources;}@Overridepublic Reference getReference() throws NamingException {Reference reference = new Reference(Config.class.getName(),ConfigObjectFactory.class.getName(),null);reference.add(new StringRefAddr("name",this.name));reference.add(new StringRefAddr("sources",this.sources));return reference;}public static boolean contains(String property){return properties.contains(property);}}2. ConfigContext.javaJNDI Context,用于维护Context中config对象实例,内部通过treeMap的方式保存了config实例与naming的关系,其中name类似于"jdbc/mysql"这种路径. 代码中有些方法没有实现,仅供参考.Java代码

import javax.naming.*;import javax.naming.spi.NamingManager;import java.util.*;import java.util.concurrent.ConcurrentHashMap;public class ConfigContext implements Context {//private Map<String, Config> bindings = new ConcurrentHashMap<String, Config>();protected static final NameParser PARSER = new NameParserImpl();private Hashtable environment = new Hashtable();protected static final String SCHEMA = "config:";static class NameParserImpl implements NameParser {public Name parse(String name) throws NamingException {return new CompositeName(name);}}private SortedMap<String,Config> bindings = new TreeMap<String, Config>();private String prefix = "";public ConfigContext(){}public ConfigContext(Hashtable environment){this.environment = environment;}protected ConfigContext(String prefix){this.prefix = prefix;}protected ConfigContext(String prefix,SortedMap<String,Config> bindings){this.prefix = prefix;this.bindings = bindings;}public Object lookup(Name name) throws NamingException {return lookup(name.toString()) ;}public Object lookup(String name) throws NamingException {String currentPath = null;if(!name.startsWith("/")){currentPath = prefix + "/" + name;} else{currentPath = prefix + name;}Config config = bindings.get(currentPath);//如果节点存在,则直接返回if(config != null){return config;}SortedMap<String,Config> tailMap = bindings.tailMap(currentPath);if(!tailMap.isEmpty()){//copySortedMap<String,Config> subBindings = new TreeMap<String, Config>();Iterator<String> it = tailMap.keySet().iterator();for(Map.Entry<String,Config> entry : tailMap.entrySet()){String path = entry.getKey();if(path.startsWith(currentPath)){subBindings.put(path,entry.getValue()) ;}}if(!subBindings.isEmpty()){return new ConfigContext(currentPath,subBindings);}}//other ,proxyint pos = name.indexOf(':');if (pos > 0) {String scheme = name.substring(0, pos);Context ctx = NamingManager.getURLContext(scheme, environment);if (ctx != null) {return ctx.lookup(name);}}return null;}public void bind(Name name, Object obj) throws NamingException {bind(name.toString(),obj);}public void bind(String name, Object obj) throws NamingException {if(!(obj instanceof Config)){return;}String currentPath = null;if(!name.startsWith("/")){currentPath = prefix + "/" + name;} else{currentPath = prefix + name;}bindings.put(currentPath,(Config)obj);}public void rebind(Name name, Object obj) throws NamingException {bind(name,obj);}public void rebind(String name, Object obj) throws NamingException {bind(name,obj);}public void unbind(Name name) throws NamingException {unbind(name.toString());}public void unbind(String name) throws NamingException {bindings.remove(name);}public void rename(Name oldName, Name newName) throws NamingException {rename(oldName.toString(), newName.toString());}public void rename(String oldName, String newName) throws NamingException {if(!bindings.containsKey(oldName)){throw new NamingException("Name of " + oldName +" don't exist") ;}if(bindings.containsKey(newName)){throw new NamingException("Name of " + newName + " has already exist.");}Config value = bindings.remove(oldName);bindings.put(newName,value);}public NameParser getNameParser(String name) throws NamingException {return PARSER;}public Name composeName(Name name, Name prefix) throws NamingException {Name result = (Name)prefix.clone();result.addAll(name);return result;}public String composeName(String name, String prefix) throws NamingException {CompositeName result = new CompositeName(prefix);result.addAll(new CompositeName(name));return result.toString();}public Object addToEnvironment(String propName, Object propVal) throws NamingException {return this.environment.put(propName,propName.toString());}public Object removeFromEnvironment(String propName) throws NamingException {return this.environment.remove(propName);}public String getNameInNamespace() throws NamingException {return "";}}3. ConfigInitialContextFactory.java实例化ConfigContext,应用程序就可以使用Context中绑定的对象.Java代码

import javax.naming.*;import javax.naming.spi.InitialContextFactory;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;import java.util.Properties;/****/public class ConfigInitialContextFactory implements InitialContextFactory {protected static final String PREFIX = "config.";protected static final String NAME_SUFFIX = ".name";protected static final String SOURCES_SUFFIX = ".sources";public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {//environment中包括了当前application中所有的JNDI配置信息//在实例化context时需要有选择性的操作.//比如,当前应用中有JMS的JNDI配置,那么此environment也包括这些信息.if (environment == null) {return new ConfigContext();}Map<String, Map<String, String>> configs = new HashMap<String, Map<String, String>>();Properties innerEnv = new Properties();for (Map.Entry entry : environment.entrySet()) {String key = (String) entry.getKey();if (!key.startsWith(PREFIX)) {continue;}int begin = key.indexOf(".");int end = key.lastIndexOf(".");if (begin == end) {continue;}String property = key.substring(end + 1);if(!Config.contains(property)){continue;}//将naming表示为类似于目录的路径,其实它可以为任意字符串.String name = key.substring(begin + 1, end).replaceAll("\\.", "/");Map<String, String> properties = configs.get(name);if (properties == null) {properties = new HashMap<String, String>();configs.put(name, properties);}String content = "";if (entry.getValue() != null) {content = entry.getValue().toString();}properties.put(property, content);innerEnv.put(name + "/" + property,content);}Context context = new ConfigContext();for (Map.Entry<String, Map<String, String>> entry : configs.entrySet()){String name = entry.getKey();Config config = createConfig(name, entry.getValue());context.bind(name, config);}return context;}private Config createConfig(String name, Map<String, String> properties) {if (name == null) {throw new RuntimeException("config name cant be empty..");}Config config = new Config(name);String sources = properties.get("sources");if (sources != null) {config.setSources(sources);}//more properties setting..return config;}public static void main(String[] args) throws Exception {Properties env = new Properties();env.put(Context.INITIAL_CONTEXT_FACTORY, "com.demo.config.jndi.ConfigInitialContextFactory");env.put("config.database.mysql.name", "mysql-jdbc");env.put("config.database.mysql.sources", "192.168.0.122:3306");Context context = new InitialContext(env);Config config = (Config) context.lookup("database/mysql");if (config != null) {System.out.println(config.getName() + "," + config.getSources());}Name name = new CompositeName("database/mysql");config = (Config) context.lookup(name);if (config != null) {System.out.println(config.getName() + "," + config.getSources());}Context subContext = (Context)context.lookup("database");config = (Config) subContext.lookup("mysql");if (config != null) {System.out.println(config.getName() + "," + config.getSources());}}}4. ConfigObjectFactory.java应用程序或者外部容器,创建对象的工厂.Java代码

import javax.naming.*;import javax.naming.spi.ObjectFactory;import java.util.*;/*** Created with IntelliJ IDEA.* User: guanqing-liu* Date: 13-11-5* Time: 下午2:58* To change this template use File | Settings | File Templates.*/public class ConfigObjectFactory implements ObjectFactory {public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {// // you should be very careful;// if (nameCtx != null && name != null) {// Object result = nameCtx.lookup(name);// if (result != null && (result instanceof Config)) {// return result;// }// }// if (name != null && environment != null) {// Context context = new InitialContext(environment);// Object result = context.lookup(name);// if (result != null && (result instanceof Config)) {// return result;// }// }//rebuild object from reference//if (!(obj instanceof Reference)) {return null;}Reference reference = (Reference) obj;//类型检测if (!Config.class.getName().equalsIgnoreCase(reference.getClassName())) {return null;}Map<String, String> properties = new HashMap<String, String>();for (String property : Config.properties) {StringRefAddr addr = (StringRefAddr) reference.get(property);if (addr != null) {properties.put(property, addr.getContent().toString());}}//buildConfig config = new Config();config.setName(properties.get("name"));config.setSources(properties.get("sources"));return config;}public static void main(String[] args) throws Exception {Reference reference = new Reference(Config.class.getName(), ConfigObjectFactory.class.getName(), null);reference.add(new StringRefAddr("name", "mysql-jdbc"));reference.add(new StringRefAddr("sources", "192.168.0.122:3306"));Config config = (Config) new ConfigObjectFactory().getObjectInstance(reference, null, null, null);System.out.println(config.getName() + "<>" + config.getSources());}}5. spring配置1) config-jndi.properties文件Xml代码

//config-jndi.properties文件java.naming.factory.initial=com.demo.config.jndi.ConfigInitialContextFactoryjava.naming.factory.object=com.demo.config.jndi.ConfigObjectFactoryconfig.server.zookeeper.name=zookeeperconfig.server.zookeeper.sources=192.168.0.15:2181config.server.mysql.name=mysqlconfig.server.mysql.sources=192.168.0.15:33062) spring.xml配置Java代码

<bean id="configEnv" class="org.springframework.beans.factory.config.PropertiesFactoryBean"><property name="locations" value="classpath:config-jndi.properties"/></bean><bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate"><property name="environment" ref="configEnv"/></bean><bean id="zookeeperConfig" class="org.springframework.jndi.JndiObjectFactoryBean"><property name="jndiName" value="server/zookeeper"/></bean><bean id="mysqlConfig" class="org.springframework.jndi.JndiObjectFactoryBean"><property name="jndiName" value="server/mysql"/></bean>3) 测试类Java代码

public class JNDISpringMain {public static void main(String[] args){ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");Config config = (Config)context.getBean("zookeeperConfig");System.out.println(config.getName() + "<>" + config.getSources());}}
