对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.
 * <p>
 * It is used by the {@code URL} class to create a
 * {@code URLStreamHandler} for a specific protocol.
 *
 * @author  Arthur van Hoff
 * @see     java.net.URL
 * @see     java.net.URLStreamHandler
 * @since   JDK1.0
 */
public interface URLStreamHandlerFactory {
    /**
     * Creates a new {@code URLStreamHandler} instance with the specified
     * protocol.
     *
     * @param   protocol   the protocol ("{@code ftp}",
     *                     "{@code http}", "{@code nntp}", etc.).
     * @return  a {@code URLStreamHandler} for the specific protocol.
     * @see     java.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()) {//遍历pkgs

		String 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) 两个方法即可

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值