最近研究微信的公众平台开发,需要和微信的http://www.wfuyu.com/server/进行数据读取,简单研究了下jdk自带的HttpUrlConnection类(URLConnection的子类),简单实现了1下微信的http://www.wfuyu.com/access/_token获得。
获得微信http://www.wfuyu.com/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⑻");
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流都单独定义援用是为了测试流关闭的问题,最后只关闭最外层流,由于外层流关闭的时候就将包裹的流1并关闭了。见下面的BufferedReader类的close方法:
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
in.close();
in = null;
cb = null;
}
}
其中,in就是包裹的流。
下面贴post方法的代码,然后综合比较1下,代码:
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⑻");
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.思路是1样的:首先建立链接对象(URL)->由链接对象得到代理对象(HttpUrlConnection)->设置1些链接参数->启动链接通道(conn.connect();)->进行输出输入操作
2.get方法参数是带在链接上的,所以没有单独赋值操作,直接在启动链接通道后得到输入流便可得到返回值
3.post方法参数不带在链接上,需要单独赋值(赋值是使用输出流来进行的),需要将输出打开(conn.setDoOutput(true);)由于URLConnection默许输出是false;而input是默许打开的,故不用重复设置。
4.读取返回值方式1样。
在研究进程中,对URLConnection怎样得到的InputStream比较好奇,查看源码为:
public InputStream getInputStream() throws IOException {
throw new UnknownServiceException("protocol doesn't support input");
}
所以很迷惑,是jdk1.7 。等下来研究出来再补上来。
共勉!
-----------------------------------------------------------------
紧跟上面,对jdk怎样实现的getInputStream和getOutputStream和其他1些方法,经过研究,终究找到了。接下来释疑:
需要注意到:
URL url = new URL(ACCESS_TOKEN_WEIXIN);
conn = (HttpURLConnection)url.openConnection();
这里得到HttpUrlConnection的时候是使用了强转得到的,故跟踪到URL类的openConnection方法内->
public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}
这里的handler定义在URL类的上方->
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的同包下有1个同名类,果然发现该类,源码 ->
public class HttpURLConnection extends java.net.HttpURLConnection
这是关建行,该类继承了java.net.HttpURLConnection类。所以返回该类可以由java.net.HttpURLConnection类的父类URLConnection接到,然后强转成java.net.HttpURLConnection类。设计10分奇妙->
@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属性的设置也在这里起到了作用。艾玛,终究找到实现的地方了,其他方法原理1致了。
略微整理下:全部的类流转进程就是:sun.net.www.protocol.http.HttpURLConnection转化成java.net.URLConnection,然后强转成java.net.HttpURLConnection。
晕乎~