对Java的URL类支持的协议进行扩展的方法

JAVA默认提供了对file,ftp,gopher,http,https,jar,mailto,netdoc协议的支持。当我们要利用这些协议来创建应用时,主要会涉及到如下几个类:java.net.URL、java.net.URLConnection、InputStream。URL类默认支持上述协议,但是有时候我们想自定义协议,怎么办呢?


Java提供了三种方法可以支持这个扩展
1、URL.setURLStreamHandlerFactory(URLStreamHandlerFactory) URLStreamHandlerFactory(java.net.URLStreamHandlerFactory),这是一个接口,定义如下:

package java.net; /** * This interface defines a factory for {@code URL} stream * protocol handlers. * * It is used by the {@code URL} class to create a * {@code URLStreamHandler} for a specific protocol. * * @authorArthur van Hoff * @seejava.net.URL * @seejava.net.URLStreamHandler * @sinceJDK1.0 */ public interface URLStreamHandlerFactory { /** * Creates a new {@code URLStreamHandler} instance with the specified * protocol. * * @paramprotocolthe protocol ("{@code ftp}", *"{@code http}", "{@code nntp}", etc.). * @returna {@code URLStreamHandler} for the specific protocol. * @seejava.net.URLStreamHandler */ URLStreamHandler createURLStreamHandler(String protocol); }

此接口需要实现createURLStreamHandler(String protocol)方法,参数protocol为协议名称,返回URLStreamHandler(java.net.URLStreamHandler)抽象类,抽象方法定义如下:

abstract protected URLConnection openConnection(URL u) throws IOException;

参数u为URL类型,URL.openConnection间接调用这个方法,返回URLConnection,然后可以获取InputStream进而获取相应的数据(资源)
示例如下:

URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() { @Override public URLStreamHandler createURLStreamHandler(String protocol) {if("json".equals(protocol)){ return new URLStreamHandler(){@Override protected URLConnection openConnection(URL url) throws IOException {return new URLConnection(url){public InputStream getInputStream() throws IOException { return new FileInputStream("d:/aaaa.txt"); }@Override public void connect() throws IOException { //建立连接 } }; } }; } else return null; } }); URL url = new URL("json://json.url.com"); InputStream in = url.openConnection().getInputStream(); System.out.println(in.read());

上述代码判断如果协议(protocal)为json,则返回一个自定义的URLStreamHandler,否则返回null,对应其他Java本身已经支持的协议会不会造成影响呢?
我们且看URL的一个构造方法(URL(String protocol, String host, int port, String file,URLStreamHandler handler) 中类似):

public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException { String original = spec; int i, limit, c; int start = 0; String newProtocol = null; boolean aRef=false; boolean isRelative = false; // Check for permission to specify a handler if (handler != null) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkSpecifyHandler(sm); } } try { limit = spec.length(); while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) { limit--; //eliminate trailing whitespace } while ((start < limit) && (spec.charAt(start) <= ' ')) { start++; // eliminate leading whitespace }if (spec.regionMatches(true, start, "url:", 0, 4)) { start += 4; } if (start < spec.length() && spec.charAt(start) == '#') { /* we're assuming this is a ref relative to the context URL. * This means protocols cannot start w/ '#', but we must parse * ref URL's like: "hello:there" w/ a ':' in them. */ aRef=true; } for (i = start ; !aRef && (i < limit) && ((c = spec.charAt(i)) != '/') ; i++) { if (c == ':') {String s = spec.substring(start, i).toLowerCase(); if (isValidProtocol(s)) { newProtocol = s; start = i + 1; } break; } }// Only use our context if the protocols match. protocol = newProtocol; if ((context != null) && ((newProtocol == null) || newProtocol.equalsIgnoreCase(context.protocol))) { // inherit the protocol handler from the context // if not specified to the constructor if (handler == null) { handler = context.handler; }// If the context is a hierarchical URL scheme and the spec // contains a matching scheme then maintain backwards // compatibility and treat it as if the spec didn't contain // the scheme; see 5.2.3 of RFC2396 if (context.path != null && context.path.startsWith("/")) newProtocol = null; if (newProtocol == null) { protocol = context.protocol; authority = context.authority; userInfo = context.userInfo; host = context.host; port = context.port; file = context.file; path = context.path; isRelative = true; } }if (protocol == null) { throw new MalformedURLException("no protocol: "+original); }// Get the protocol handler if not specified or the protocol // of the context could not be used if (handler == null && (handler = getURLStreamHandler(protocol)) == null) { throw new MalformedURLException("unknown protocol: "+protocol); }this.handler = handler; i = spec.indexOf('#', start); if (i >= 0) { ref = spec.substring(i + 1, limit); limit = i; }/* * Handle special case inheritance of query and fragment * implied by RFC2396 section 5.2.2. */ if (isRelative && start == limit) { query = context.query; if (ref == null) { ref = context.ref; } }handler.parseURL(this, spec, start, limit); } catch(MalformedURLException e) { throw e; } catch(Exception e) { MalformedURLException exception = new MalformedURLException(e.getMessage()); exception.initCause(e); throw exception; } }

代码87行,调用了getURLStreamHandler(protocol),此方法:

static URLStreamHandler getURLStreamHandler(String protocol) { URLStreamHandler handler = handlers.get(protocol); if (handler == null) {boolean checkedWithFactory = false; // Use the factory (if any) if (factory != null) { handler = factory.createURLStreamHandler(protocol); checkedWithFactory = true; }// Try java protocol handler if (handler == null) { String packagePrefixList = null; packagePrefixList = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction( protocolPathProp,"")); if (packagePrefixList != "") { packagePrefixList += "|"; }// REMIND: decide whether to allow the "null" class prefix // or not. packagePrefixList += "sun.net.www.protocol"; StringTokenizer packagePrefixIter = new StringTokenizer(packagePrefixList, "|"); while (handler == null && packagePrefixIter.hasMoreTokens()) {String packagePrefix = packagePrefixIter.nextToken().trim(); try { String clsName = packagePrefix + "." + protocol + ".Handler"; Class cls = null; try { cls = Class.forName(clsName); } catch (ClassNotFoundException e) { ClassLoader cl = ClassLoader.getSystemClassLoader(); if (cl != null) { cls = cl.loadClass(clsName); } } if (cls != null) { handler= (URLStreamHandler)cls.newInstance(); } } catch (Exception e) { // any number of exceptions can get thrown here } } }synchronized (streamHandlerLock) {URLStreamHandler handler2 = null; // Check again with hashtable just in case another // thread created a handler since we last checked handler2 = handlers.get(protocol); if (handler2 != null) { return handler2; }// Check with factory if another thread set a // factory since our last check if (!checkedWithFactory && factory != null) { handler2 = factory.createURLStreamHandler(protocol); }if (handler2 != null) { // The handler from the factory must be given more // importance. Discard the default handler that // this thread created. handler = handler2; }// Insert this handler into the hashtable if (handler != null) { handlers.put(protocol, handler); }} } return handler; }

代码段
if (factory != null) {
handler = factory.createURLStreamHandler(protocol);
checkedWithFactory = true;
}
这一段是从factory中获取相应协议的URLStreamHandler,如果获取不到,则从另一渠道获得(即不会对java已经支持的协议造成影响),就是我们讲的第2种方法
各个构造方法的调用关系代码如下:

//#1 public URL(String spec) throws MalformedURLException { this(null, spec); //调用#2 URL(URL context, String spec) } //#2 public URL(URL context, String spec) throws MalformedURLException { this(context, spec, null); 调用#6 URL(URL context, String spec, URLStreamHandler handler) }//#3 public URL(String protocol, String host, int port, String file) throws MalformedURLException { this(protocol, host, port, file, null); //调用#6 RL(String protocol, String host, int port, String file,URLStreamHandler handler) }//#4 public URL(String protocol, String host, String file) throws MalformedURLException { this(protocol, host, -1, file); //调用#3 URL(String protocol, String host, int port, String file) }//#5 public URL(String protocol, String host, int port, String file, URLStreamHandler handler)throws MalformedURLException{ //.... }//#6 public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException{ //.... }//可以看出,实质性逻辑都在#5和#6方法

2、 通过 JVM 启动参数 -Djava.protocol.handler.pkgs来设置 URLStreamHandler 实现类的包路径比如-D java.protocol.handler.pkgs=com.myprotocol.pkgs0|com.myprotocol.pkgs1,多个用|分割,java默认的包为sun.net.www.protocol,设置了这个参数,会拼接在默认的包之后,即sun.net.www.protocol|com.myprotocol.pkgs0|com.myprotocol.pkgs1 看这段代码
if (handler == null) { String packagePrefixList = null; packagePrefixList = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction( protocolPathProp,"")); //protocolPathProp的值为java.protocol.handler.pkgs if (packagePrefixList != "") { packagePrefixList += "|"; } // REMIND: decide whether to allow the "null" class prefix // or not. packagePrefixList += "sun.net.www.protocol"; //拼接默认的pkgs StringTokenizer packagePrefixIter = new StringTokenizer(packagePrefixList, "|"); while (handler == null && packagePrefixIter.hasMoreTokens()) {//遍历pkgsString packagePrefix = packagePrefixIter.nextToken().trim(); try { String clsName = packagePrefix + "." + protocol + ".Handler"; //类全名为pkgs.protocal.Handler Class cls = null; try { cls = Class.forName(clsName); } catch (ClassNotFoundException e) { ClassLoader cl = ClassLoader.getSystemClassLoader(); if (cl != null) { cls = cl.loadClass(clsName); } } if (cls != null) { handler= (URLStreamHandler)cls.newInstance(); } } catch (Exception e) { // any number of exceptions can get thrown here } } }

类的命名模式为 [pkgs].[protocol].Handler,比如默认实现” sun.net.www.protocol.[protocol].Handler”, 比如HTTP 协议的对应的处理类名为 -sun.net. www.protocol.http.Handler
自定义协议例子如下:
package com.myprotocol.pkgs0.json; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; public class Handler extends URLStreamHandler { @Override protected URLConnection openConnection(URL u) throws IOException {return new URLConnection(u) {public InputStream getInputStream() throws IOException { return new FileInputStream("d:/aaaa.txt"); }@Override public void connect() throws IOException { // 建立连接 } }; } }

启动时命令:java -Djava.protocol.handler.pkgs=com.myprotocol.pkgs0 其他参数 主类
3、构造方法URL((URL)null, "json://www.google.com",new URLStreamHandler(){...}) 这种方法直接设置Handler,比较简单,不在赘述


代理Proxy
URLStreamHandler 覆盖openConnection(URL) 和openConnection(URL,Proxy) 两个方法即可


【对Java的URL类支持的协议进行扩展的方法】

    推荐阅读