近期研究微信的公众平台开发。须要和微信的server进行数据读取,简单研究了下jdk自带的HttpUrlConnection类(URLConnection的子类),简单实现了一下微信的access_token获取。
获取微信access_token的地址:微信地址
该地址是get方法请求就可以。先贴代码,doGet:
private final String ACCESS_TOKEN_WEIXIN = "https://api.weixin.qq.com/cgi-bin/token"; public String doGet(String grantType, String appId, String secret){ String url = ACCESS_TOKEN_WEIXIN+"?grant_type="+grantType+ "&appid="+appId+"&secret="+secret; HttpURLConnection conn = null; InputStream is = null; InputStreamReader reader = null; BufferedReader br = null; String str = ""; try { URL weiUrl = new URL(url); conn = (HttpURLConnection)weiUrl.openConnection(); conn.setRequestProperty("connection", "Keep-Alive"); conn.connect(); is = conn.getInputStream(); reader = new InputStreamReader(is, "UTF-8"); br = new BufferedReader(reader); String readLine = ""; while((readLine=br.readLine())!=null){ str+=readLine+" "; } } catch (Exception e) { e.printStackTrace(); }finally{ try{ if(br!=null){ br.close(); } if(conn!=null){ conn.disconnect(); } }catch(Exception e1){ e1.printStackTrace(); } } return str; }
我将InputStream流和InputStreamReader流都单独定义引用是为了測试流关闭的问题,最后仅仅关闭最外层流,由于外层流关闭的时候就将包裹的流一并关闭了。见以下的BufferedReader类的close方法:
public void close() throws IOException { synchronized (lock) { if (in == null) return; in.close(); in = null; cb = null; } }当中,in就是包裹的流。
以下贴post方法的代码。然后综合比較一下,代码:
public String doPost(String grantType, String appId, String secret){ HttpURLConnection conn = null; InputStream is = null; InputStreamReader reader = null; BufferedReader br = null; String str = ""; try { URL url = new URL(ACCESS_TOKEN_WEIXIN); conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("POST"); conn.setDoOutput(true);//默觉得false的,所以须要设置 conn.setUseCaches(false); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.connect(); OutputStream outStream = conn.getOutputStream(); DataOutputStream out = new DataOutputStream(outStream); String params = "grant_type="+grantType+ "&appid="+appId+"&secret="+secret; out.writeBytes(params); out.close(); is = conn.getInputStream(); reader = new InputStreamReader(is, "UTF-8"); br = new BufferedReader(reader); String readLine = ""; while((readLine=br.readLine())!=null){ str+=readLine+" "; } } catch (Exception e) { e.printStackTrace(); }finally{ try{ if(br!=null){ br.close(); } if(conn!=null){ conn.disconnect(); } }catch(Exception e1){ e1.printStackTrace(); } } return str; }
两相比較:
1.思路是一样的:首先建立链接对象(URL)->由链接对象得到代理对象(HttpUrlConnection)->设置一些链接參数->启动链接通道(conn.connect();)->进行输出输入操作
2.get方法參数是带在链接上的,所以没有单独赋值操作。直接在启动链接通道后得到输入流就可以得到返回值
3.post方法參数不带在链接上,须要单独赋值(赋值是使用输出流来进行的)。须要将输出打开(conn.setDoOutput(true);)由于URLConnection默认输出是false。而input是默认打开的。故不用反复设置。
4.读取返回值方式一样。
在研究过程中,对于URLConnection如何得到的InputStream比較好奇。查看源代码为:
public InputStream getInputStream() throws IOException { throw new UnknownServiceException("protocol doesn't support input"); }
所以非常迷惑,是jdk1.7 。等下来研究出来再补上来。
共勉!
-----------------------------------------------------------------
紧跟上面。对于jdk如何实现的getInputStream和getOutputStream以及其它一些方法,经过研究。最终找到了。接下来释疑:
须要注意到:
URL url = new URL(ACCESS_TOKEN_WEIXIN); conn = (HttpURLConnection)url.openConnection();
public URLConnection openConnection() throws java.io.IOException { return handler.openConnection(this); }
transient URLStreamHandler handler;
然后去寻找handler的赋值方法getURLStreamHandler。发现例如以下代码->
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 += JDK_PACKAGE_PREFIX; StringTokenizer packagePrefixIter = new StringTokenizer(packagePrefixList, "|"); while (handler == null && packagePrefixIter.hasMoreTokens()) { String packagePrefix = packagePrefixIter.nextToken().trim(); // do not try to instantiate the JDK gopher handler // unless the system property had been explicitly set if (protocol.equalsIgnoreCase(GOPHER) && packagePrefix.equals(JDK_PACKAGE_PREFIX) && !enableGopher) { continue; } try { String clsName = packagePrefix + "." + protocol + ".Handler"; Class cls = null; try { cls = Class.forName(clsName);
当中
JDK_PACKAGE_PREFIX在类中定义的是:
private static final String JDK_PACKAGE_PREFIX = "sun.net.www.protocol";再结合以下的
String clsName = packagePrefix + "." + protocol + ".Handler";基本能够定位到handler类的实现类在sun.net.www.protocol.http.Handler。
然后百度搜索之后找到源代码(jdk下没有sun包的源代码)->
sun.net.www.protocol.http包的视图 Handler.java的源代码 ->
protected java.net.URLConnection openConnection(URL u) throws IOException { return openConnection(u, (Proxy)null); } protected java.net.URLConnection openConnection(URL u, Proxy p) throws IOException { return new HttpURLConnection(u, p, this); }
能够看到返回值是HttpURLConnection,可是发现该类中并没有引入java.net.HttpURLConnection类,故推測在Handler的同包下有一个同名类,果然发现该类,源代码 ->
public class HttpURLConnection extends java.net.HttpURLConnection
这是关建行,该类继承了java.net.HttpURLConnection类。所以返回该类能够由java.net.HttpURLConnection类的父类URLConnection接到,然后强转成java.net.HttpURLConnection类。设计十分巧妙->
@Override public synchronized OutputStream getOutputStream() throws IOException { try { if (!doOutput) { throw new ProtocolException("cannot write to a URLConnection" + " if doOutput=false - call setDoOutput(true)"); } if (method.equals("GET")) { method = "POST"; // Backward compatibility } if (!"POST".equals(method) && !"PUT".equals(method) && "http".equals(url.getProtocol())) { throw new ProtocolException("HTTP method " + method + " doesn't support output"); } // if there's already an input stream open, throw an exception if (inputStream != null) { throw new ProtocolException("Cannot write output after reading input."); } if (!checkReuseConnection()) connect();......
这就是getOutputStream的实现地方了,正好doOutput属性的设置也在这里起到了作用。
艾玛,最终找到实现的地方了。其它方法原理一致了。
略微整理下:整个的类流转过程就是:sun.net.www.protocol.http.HttpURLConnection转化成java.net.URLConnection,然后强转成java.net.HttpURLConnection。
晕乎~