PendingIntent的基本理解和使用

 android  PendingIntent的基本理解和使用已关闭评论
7月 032020
 

简书上看到的一篇PendingIntent的文章,收藏起来,链接:https://www.jianshu.com/p/a37f0ce2da2e

 

PendingIntent可以看作是对Intent的一个封装,但它不是立刻执行某个行为,而是满足某些条件或触发某些事件后才执行指定的行为(启动特定Service,Activity,BrcastReceive)。

我们可以把Pending Intent交给其他程序,其他程序按照PendingIntent进行操作。

Alarm定时器与Notification通知中都使用了PendingIntent

1.获得PendingIntent类内部静态方法获得PendingIntent实例:

//获得一个用于启动特定Activity的PendingIntent

public static PendingIntent getActivity(Context context, int requestCode,Intent intent, int flags)

//获得一个用于启动特定Service的PendingIntent

public static PendingIntent getService(Context context, int requestCode,Intent intent, int flags)

//获得一个用于发送特定Broadcast的PendingIntent

public static PendingIntent getBroadcast(Context context, int requestCode,Intent intent, int flags)

参数说明:

context:上下文对象。

requstCode:请求码,发件人的私人请求代码(当前未使用)。

intent:请求意图。用于要指明要启动的类以及数据的传递;

flags:这是一个关键的标志位:

主要常量

FLAG_CANCEL_CURRENT:如果当前系统中已经存在一个相同的PendingIntent对象,那么就将先将已有的PendingIntent取消,然后重新生成一个PendingIntent对象。

FLAG_NO_CREATE:如果当前系统中不存在相同的PendingIntent对象,系统将不会创建该PendingIntent对象而是直接返回null。

FLAG_ONE_SHOT:该PendingIntent只作用一次。在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话,系统将会返回一个SendIntentException。

FLAG_UPDATE_CURRENT:如果系统中有一个和你描述的PendingIntent对等的PendingInent,那么系统将使用该PendingIntent对象,但是会使用新的Intent来更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras。

注意:两个PendingIntent对等是指它们的operation一样, 且其它们的Intent的action, data, categories, components和flags都一样。但是它们的Intent的Extra可以不一样。

 

入门OkHttp使用

 okhttp  入门OkHttp使用已关闭评论
10月 222018
 

OkHttp入门资料,推荐!来自:https://blog.csdn.net/mynameishuangshuai/article/details/51303446

       OkHttp官网地址:http://square.github.io/okhttp/ 
       OkHttp GitHub地址:https://github.com/square/okhttp 
官网的自我介绍: 
       HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth. 
OkHttp is an HTTP client that’s efficient by default:

•   HTTP/2 support allows all requests to the same host to share a socket.
•   Connection pooling reduces request latency (if HTTP/2 isn’t available).
•   Transparent GZIP shrinks download sizes.
•   Response caching avoids the network completely for repeat requests.

       OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp initiates new connections with modern TLS features (SNI, ALPN), and falls back to TLS 1.0 if the handshake fails. 
       Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks. 
       OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7. 
       概括起来说OkHttp是一款优秀的HTTP框架,它支持get请求和post请求,支持基于Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟问题。

配置方法
(一)导入Jar包 
点击下面链接下载最新v3.2.0 JAR 
http://repo1.maven.org/maven2/com/squareup/okhttp3/okhttp/3.2.0/okhttp-3.2.0.jar 
(二)通过构建方式导入 
MAVEN

<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
  <version>3.2.0</version>
</dependency>

GRADLE

compile ‘com.squareup.okhttp3:okhttp:3.2.0’
基本要求
Requests(请求)
       每一个HTTP请求中都应该包含一个URL,一个GET或POST方法以及Header或其他参数,当然还可以含特定内容类型的数据流。

Responses(响应)
       响应则包含一个回复代码(200代表成功,404代表未找到),Header和定制可选的body。

基本使用
       在日常开发中最常用到的网络请求就是GET和POST两种请求方式。

HTTP GET
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
    Request request = new Request.Builder().url(url).build();
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException(“Unexpected code ” + response);
    }
}
Request是OkHttp中访问的请求,Builder是辅助类,Response即OkHttp中的响应。 
Response类:

public boolean isSuccessful()
Returns true if the code is in [200..300), which means the request was successfully received, understood, and accepted.
response.body()返回ResponseBody类
可以方便的获取string

public final String string() throws IOException
Returns the response as a string decoded with the charset of the Content-Type header. If that header is either absent or lacks a charset, this will attempt to decode the response body as UTF-8.
Throws:
IOException
当然也能获取到流的形式: 
public final InputStream byteStream()

HTTP POST
POST提交Json数据

public static final MediaType JSON = MediaType.parse(“application/json; charset=utf-8”);
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
    RequestBody body = RequestBody.create(JSON, json);
    Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
    Response response = client.newCall(request).execute();
    f (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException(“Unexpected code ” + response);
    }
}
使用Request的post方法来提交请求体RequestBody 
POST提交键值对 
OkHttp也可以通过POST方式把键值对数据传送到服务器

OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
    RequestBody formBody = new FormEncodingBuilder()
    .add(“platform”, “android”)
    .add(“name”, “bug”)
    .add(“subject”, “XXXXXXXXXXXXXXX”)
    .build();

    Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();

    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException(“Unexpected code ” + response);
    }
}
案例
布局文件:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
              android:layout_width=”match_parent”
              android:layout_height=”match_parent”
              android:orientation=”vertical”>

    <LinearLayout
        android:layout_width=”match_parent”
        android:layout_height=”wrap_content”
        android:gravity=”center_horizontal”
        android:orientation=”horizontal”>

        <Button
            android:id=”@+id/bt_get”
            android:layout_width=”wrap_content”
            android:layout_height=”wrap_content”
            android:text=”乌云Get请求”/>

        <Button
            android:id=”@+id/bt_post”
            android:layout_width=”wrap_content”
            android:layout_height=”wrap_content”
            android:text=”乌云Post请求”/>

    </LinearLayout>

    <TextView
        android:id=”@+id/tv_show”
        android:layout_width=”match_parent”
        android:layout_height=”wrap_content”/>
</LinearLayout>
 
Java代码: 
由于android本身是不允许在UI线程做网络请求操作的,所以我们自己写个线程完成网络操作

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button bt_get;
    private Button bt_post;

    final OkHttpClient client = new OkHttpClient();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        bt_get=(Button)findViewById(R.id.bt_get);
        bt_post=(Button)findViewById(R.id.bt_post);

        bt_get.setOnClickListener(this);
        bt_post.setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.bt_get:
                getRequest();
                break;

            case R.id.bt_post:
                postRequest();
                break;

        }
    }

    private void getRequest() {

        final Request request=new Request.Builder()
                .get()
                .tag(this)
                .url(“http://www.wooyun.org”)
                .build();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Response response = null;
                try {
                    response = client.newCall(request).execute();
                    if (response.isSuccessful()) {
                        Log.i(“WY”,”打印GET响应的数据:” + response.body().string());
                    } else {
                        throw new IOException(“Unexpected code ” + response);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }

    private void postRequest() {

        RequestBody formBody = new FormEncodingBuilder()
                .add(“”,””)
                .build();

        final Request request = new Request.Builder()
                .url(“http://www.wooyun.org”)
                .post(formBody)
                .build();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Response response = null;
                try {
                    response = client.newCall(request).execute();
                    if (response.isSuccessful()) {
                        Log.i(“WY”,”打印POST响应的数据:” + response.body().string());
                    } else {
                        throw new IOException(“Unexpected code ” + response);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }

}
执行结果: 

官方Recipes
Synchronous Get(同步Get)
下载一个文件,打印他的响应头,以string形式打印响应体。 
       响应体的 string() 方法对于小文档来说十分方便、高效。但是如果响应体太大(超过1MB),应避免适应 string()方法 ,因为他会将把整个文档加载到内存中。对于超过1MB的响应body,应使用流的方式来处理body。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://publicobject.com/helloworld.txt”)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    Headers responseHeaders = response.headers();
    for (int i = 0; i < responseHeaders.size(); i++) {
      System.out.println(responseHeaders.name(i) + “: ” + responseHeaders.value(i));
    }

    System.out.println(response.body().string());
  }
Asynchronous Get(异步Get)
       在一个工作线程中下载文件,当响应可读时回调Callback接口。读取响应时会阻塞当前线程。OkHttp现阶段不提供异步api来接收响应体。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://publicobject.com/helloworld.txt”)
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

        Headers responseHeaders = response.headers();
        for (int i = 0, size = responseHeaders.size(); i < size; i++) {
          System.out.println(responseHeaders.name(i) + “: ” + responseHeaders.value(i));
        }

        System.out.println(response.body().string());
      }
    });
  }
Accessing Headers(提取响应头)
典型的HTTP头 像是一个 Map

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“https://api.github.com/repos/square/okhttp/issues”)
        .header(“User-Agent”, “OkHttp Headers.java”)
        .addHeader(“Accept”, “application/json; q=0.5”)
        .addHeader(“Accept”, “application/vnd.github.v3+json”)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(“Server: ” + response.header(“Server”));
    System.out.println(“Date: ” + response.header(“Date”));
    System.out.println(“Vary: ” + response.headers(“Vary”));
  }
Posting a String(Post方式提交String)
       使用HTTP POST提交请求到服务。这个例子提交了一个markdown文档到web服务,以HTML方式渲染markdown。因为整个请求体都在内存中,因此避免使用此api提交大文档(大于1MB)。

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse(“text/x-markdown; charset=utf-8”);

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    String postBody = “”
        + “Releases\n”
        + “——–\n”
        + “\n”
        + ” * _1.0_ May 6, 2013\n”
        + ” * _1.1_ June 15, 2013\n”
        + ” * _1.2_ August 11, 2013\n”;

    Request request = new Request.Builder()
        .url(“https://api.github.com/markdown/raw”)
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }

Post Streaming(Post方式提交流)

       以流的方式POST提交请求体。请求体的内容由流写入产生。这个例子是流直接写入Okio的BufferedSink。你的程序可能会使用OutputStream,你可以使用BufferedSink.outputStream()来获取。.

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse(“text/x-markdown; charset=utf-8”);

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8(“Numbers\n”);
        sink.writeUtf8(“——-\n”);
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(” * %s = %s\n”, i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + ” × ” + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url(“https://api.github.com/markdown/raw”)
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }
Posting a File(Post方式提交文件)
以文件作为请求体是十分简单的。

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse(“text/x-markdown; charset=utf-8”);

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    File file = new File(“README.md”);

    Request request = new Request.Builder()
        .url(“https://api.github.com/markdown/raw”)
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }
Posting form parameters(Post方式提交表单)
       使用FormEncodingBuilder来构建和HTML标签相同效果的请求体。键值对将使用一种HTML兼容形式的URL编码来进行编码。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody formBody = new FormBody.Builder()
        .add(“search”, “Jurassic Park”)
        .build();
    Request request = new Request.Builder()
        .url(“https://en.wikipedia.org/w/index.php”)
        .post(formBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }
Posting a multipart request(Post方式提交分块请求)
       MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition。如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。

private static final String IMGUR_CLIENT_ID = “…”;
  private static final MediaType MEDIA_TYPE_PNG = MediaType.parse(“image/png”);

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart(“title”, “Square Logo”)
        .addFormDataPart(“image”, “logo-square.png”,
            RequestBody.create(MEDIA_TYPE_PNG, new File(“website/static/logo-square.png”)))
        .build();

    Request request = new Request.Builder()
        .header(“Authorization”, “Client-ID ” + IMGUR_CLIENT_ID)
        .url(“https://api.imgur.com/3/image”)
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }
Parse a JSON Response With Gson(使用GSON解析JSON响应)
       Gson是一个在JSON和Java对象之间转换非常方便的api。这里我们用Gson来解析Github API的JSON响应。 
注意:ResponseBody.charStream()使用响应头Content-Type指定的字符集来解析响应体。默认是UTF-8。

private final OkHttpClient client = new OkHttpClient();
  private final Gson gson = new Gson();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“https://api.github.com/gists/c2a7c39532239ff261be”)
        .build();
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
    for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
      System.out.println(entry.getKey());
      System.out.println(entry.getValue().content);
    }
  }

  static class Gist {
    Map<String, GistFile> files;
  }

  static class GistFile {
    String content;
  }
Response Caching(响应缓存)
       为了缓存响应,你需要一个你可以读写的缓存目录,和缓存大小的限制。这个缓存目录应该是私有的,不信任的程序应不能读取缓存内容。 
       一个缓存目录同时拥有多个缓存访问是错误的。大多数程序只需要调用一次new OkHttp(),在第一次调用时配置好缓存,然后其他地方只需要调用这个实例就可以了。否则两个缓存示例互相干扰,破坏响应缓存,而且有可能会导致程序崩溃。 
       响应缓存使用HTTP头作为配置。你可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持。你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600。

private final OkHttpClient client;

  public CacheResponse(File cacheDirectory) throws Exception {
    int cacheSize = 10 * 1024 * 1024; // 10 MiB
    Cache cache = new Cache(cacheDirectory, cacheSize);

    client = new OkHttpClient.Builder()
        .cache(cache)
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://publicobject.com/helloworld.txt”)
        .build();

    Response response1 = client.newCall(request).execute();
    if (!response1.isSuccessful()) throw new IOException(“Unexpected code ” + response1);

    String response1Body = response1.body().string();
    System.out.println(“Response 1 response:          ” + response1);
    System.out.println(“Response 1 cache response:    ” + response1.cacheResponse());
    System.out.println(“Response 1 network response:  ” + response1.networkResponse());

    Response response2 = client.newCall(request).execute();
    if (!response2.isSuccessful()) throw new IOException(“Unexpected code ” + response2);

    String response2Body = response2.body().string();
    System.out.println(“Response 2 response:          ” + response2);
    System.out.println(“Response 2 cache response:    ” + response2.cacheResponse());
    System.out.println(“Response 2 network response:  ” + response2.networkResponse());

    System.out.println(“Response 2 equals Response 1? ” + response1Body.equals(response2Body));
  }
       为了防止使用缓存的响应,可以用CacheControl.FORCE_NETWORK。为了防止它使用网络,使用CacheControl.FORCE_CACHE。需要注意的是:如果您使用FORCE_CACHE和网络的响应需求,OkHttp则会返回一个504提示,告诉你不可满足请求响应。 
       Canceling a Call(取消一个Call) 
       使用Call.cancel()可以立即停止掉一个正在执行的call。如果一个线程正在写请求或者读响应,将会引发IOException。当call没有必要的时候,使用这个api可以节约网络资源。例如当用户离开一个应用时。不管同步还是异步的call都可以取消。 
       你可以通过tags来同时取消多个请求。当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。.

 private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://httpbin.org/delay/2”) // This URL is served with a 2 second delay.
        .build();

    final long startNanos = System.nanoTime();
    final Call call = client.newCall(request);

    // Schedule a job to cancel the call in 1 second.
    executor.schedule(new Runnable() {
      @Override public void run() {
        System.out.printf(“%.2f Canceling call.%n”, (System.nanoTime() – startNanos) / 1e9f);
        call.cancel();
        System.out.printf(“%.2f Canceled call.%n”, (System.nanoTime() – startNanos) / 1e9f);
      }
    }, 1, TimeUnit.SECONDS);

    try {
      System.out.printf(“%.2f Executing call.%n”, (System.nanoTime() – startNanos) / 1e9f);
      Response response = call.execute();
      System.out.printf(“%.2f Call was expected to fail, but completed: %s%n”,
          (System.nanoTime() – startNanos) / 1e9f, response);
    } catch (IOException e) {
      System.out.printf(“%.2f Call failed as expected: %s%n”,
          (System.nanoTime() – startNanos) / 1e9f, e);
    }
  }
Timeouts(超时)
       没有响应时使用超时结束call。没有响应的原因可能是客户点链接问题、服务器可用性问题或者这之间的其他东西。OkHttp支持连接,读取和写入超时。

private final OkHttpClient client;

  public ConfigureTimeouts() throws Exception {
    client = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://httpbin.org/delay/2”) // This URL is served with a 2 second delay.
        .build();

    Response response = client.newCall(request).execute();
    System.out.println(“Response completed: ” + response);
  }
Per-call Configuration(每个Call的配置)
       使用OkHttpClient,所有的HTTP Client配置包括代理设置、超时设置、缓存设置。当你需要为单个call改变配置的时候,clone 一个 OkHttpClient。这个api将会返回一个浅拷贝(shallow copy),你可以用来单独自定义。下面的例子中,我们让一个请求是500ms的超时、另一个是3000ms的超时。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://httpbin.org/delay/1”) // This URL is served with a 1 second delay.
        .build();

    try {
      // Copy to customize OkHttp for this request.
      OkHttpClient copy = client.newBuilder()
          .readTimeout(500, TimeUnit.MILLISECONDS)
          .build();

      Response response = copy.newCall(request).execute();
      System.out.println(“Response 1 succeeded: ” + response);
    } catch (IOException e) {
      System.out.println(“Response 1 failed: ” + e);
    }

    try {
      // Copy to customize OkHttp for this request.
      OkHttpClient copy = client.newBuilder()
          .readTimeout(3000, TimeUnit.MILLISECONDS)
          .build();

      Response response = copy.newCall(request).execute();
      System.out.println(“Response 2 succeeded: ” + response);
    } catch (IOException e) {
      System.out.println(“Response 2 failed: ” + e);
    }
  }
Handling authentication(处理验证)
       OkHttp会自动重试未验证的请求。当响应是401 Not Authorized时,Authenticator会被要求提供证书。Authenticator的实现中需要建立一个新的包含证书的请求。如果没有证书可用,返回null来跳过尝试。

 private final OkHttpClient client;

  public Authenticate() {
    client = new OkHttpClient.Builder()
        .authenticator(new Authenticator() {
          @Override public Request authenticate(Route route, Response response) throws IOException {
            System.out.println(“Authenticating for response: ” + response);
            System.out.println(“Challenges: ” + response.challenges());
            String credential = Credentials.basic(“jesse”, “password1”);
            return response.request().newBuilder()
                .header(“Authorization”, credential)
                .build();
          }
        })
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://publicobject.com/secrets/hellosecret.txt”)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }
To avoid making many retries when authentication isn’t working, you can return null to give up. For example, you may want to skip the retry when these exact credentials have already been attempted:

if (credential.equals(response.request().header(“Authorization”))) {
    return null; // If we already failed with these credentials, don’t retry.
   }
You may also skip the retry when you’ve hit an application-defined attempt limit:
  if (responseCount(response) >= 3) {
    return null; // If we’ve failed 3 times, give up.
  }
This above code relies on this responseCount() method:
  private int responseCount(Response response) {
    int result = 1;
    while ((response = response.priorResponse()) != null) {
      result++;
    }
    return result;
  }
OkHttp官方文档:https://github.com/square/okhttp/wiki 
参考链接:http://www.cnblogs.com/ct2011/p/3997368.html

Google Protocol Buffers 入门(protobuf tutorial译文)

 protobuf  Google Protocol Buffers 入门(protobuf tutorial译文)已关闭评论
6月 122016
 

分享好文, 转自:http://www.cnblogs.com/shitouer/archive/2013/04/10/google-protocol-buffers-tutorial.html , google-protocol-buffer-tutorial的中文翻译文章!


1. 前言

这篇入门教程是基于Java语言的,这篇文章我们将会:

  1. 创建一个.proto文件,在其内定义一些PB message
  2. 使用PB编译器
  3. 使用PB Java API 读写数据

这篇文章仅是入门手册,如果想深入学习及了解,可以参看: Protocol Buffer Language GuideJava API ReferenceJava Generated Code Guide, 以及Encoding Reference

2. 为什么使用Protocol Buffers

接下来用“通讯簿”这样一个非常简单的应用来举例。该应用能够写入并读取“联系人”信息,每个联系人由name,ID,email address以及contact photo number组成。这些信息的最终存储在文件中。

如何序列化并检索这样的结构化数据呢?有以下解决方案:

  1.  使用Java序列化(Java Serialization)。这是最直接的解决方式,因为该方式是内置于Java语言的,但是,这种方式有许多问题(Effective Java 对此有详细介绍),而且当有其他应用程序(比如C++ 程序及Python程序书写的应用)与之共享数据的时候,这种方式就不能工作了。
  2. 将数据项编码成一种特殊的字符串。例如将四个整数编码成“12:3:-23:67”。这种方法简单且灵活,但是却需要编写独立的,只需要用一次的编码和解码代码,并且解析过程需要一些运行成本。这种方式对于简单的数据结构非常有效。
  3. 将数据序列化为XML。这种方式非常诱人,因为易于阅读(某种程度上)并且有不同语言的多种解析库。在需要与其他应用或者项目共享数据的时候,这是一种非常有效的方式。但是,XML是出了名的耗空间,在编码解码上会有很大的性能损耗。而且呢,操作XML DOM数非常的复杂,远不如操作类中的字段简单。

Protocol Buffers可以灵活,高效且自动化的解决该问题,只需要:

  1. 创建一个.proto 文件,描述希望数据存储结构
  2. 使用PB compiler 创建一个类,该类可以高效的,以二进制方式自动编码和解析PB数据

该生成类提供组成PB数据字段的getter和setter方法,甚至考虑了如何高效的读写PB数据。更厉害的是,PB友好的支持字段拓展,拓展后的代码,依然能够正确的读取原来格式编码的数据。

3. 定义协议格式

首先需要创建一个.proto文件。非常简单,每一个需要序列化的数据结构,编码一个PB message,然后为message中的字段指明一个名字和类型即可。该“通讯簿”的.proto 文件addressbook.proto定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package tutorial;
 
option java_package = “com.example.tutorial”;
option java_outer_classname = “AddressBookProtos”;
 
message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
 
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
 
  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }
 
  repeated PhoneNumber phone = 4;
}
message AddressBook {
  repeated Person person = 1;
}

可以看到,语法非常类似Java或者C++,接下来,我们一条一条来过一遍每句话的含义:

  • .proto文件以一个package声明开始。该声明有助于避免不同项目建设的命名冲突。Java版的PB,在没有指明java_package的情况下,生成的类默认的package即为此package。这里我们生命的java_package,所以最终生成的类会位于com.example.tutorial package下。这里需要强调一下,即使指明了java_package,我们建议依旧定义.proto文件的package。
  • 在package声明之后,紧接着是专门为java指定的两个选项:java_package 以及 java_outer_classname。java_package我们已经说过,不再赘述。java_outer_classname为生成类的名字,该类包含了所有在.proto中定义的类。如果该选项不显式指明的话,会按照驼峰规则,将.proto文件的名字作为该类名。例如“addressbook.proto”将会是“Addressbook”,“address_book.proto”即为“AddressBook”
  • java指定选项后边,即为message定义。每个message是一个包含了一系列指明了类型的字段的集合。这里的字段类型包含大多数的标准简单数据类型,包括bool,int32,float,double以及string。Message中也可以定义嵌套的message,例如“Person” message 包含“PhoneNumber” message。也可以将已定义的message作为新的数据类型,例如上例中,PhoneNumber类型在Person内部定义,但他是phone的type。在需要一个字段包含预先定义的一个列表的时候,也可以定义枚举类型,例如“PhoneType”。
  • 我们注意到, 每一个message中的字段,都有“=1”,“=2”这样的标记,这可不是初始化赋值,该值是message中,该字段的唯一标示符,在二进制编码时候会用到。数字1~15的表示需求少于一个字节,所以在编码的时候,有这样一个优化,你可以用1~15标记最常使用或者重复字段元素(repeated elements)。用16或者更大的数字来标记不太常用的可选元素。再重复字段中,每一个元素都需重复编码标签数字,所以,该优化对重复字段最佳(repeat fileds)。

message的没一个字段,都要用如下的三个修饰符(modifier)来声明:

  1. required:必须赋值,不能为空,否则该条message会被认为是“uninitialized”。build一个“uninitialized” message会抛出一个RuntimeException异常,解析一条“uninitialized” message会抛出一条IOException异常。除此之外,“required”字段跟“optional”字段并无差别。
  2. optional:字段可以赋值,也可以不赋值。假如没有赋值的话,会被赋上默认值。对于简单类型,默认值可以自己设定,例如上例的PhoneNumber中的PhoneType字段。如果没有自行设定,会被赋上一个系统默认值,数字类型会被赋为0,String类型会被赋为空字符串,bool类型会被赋为false。对于内置的message,默认值为该message的默认实例或者原型,即其内所有字段均为设置。当获取没有显式设置值的optional字段的值时,就会返回该字段的默认值。
  3. repeated:该字段可以重复任意次数,包括0次。重复数据的顺序将会保存在protocol buffer中,将这个字段想象成一个可以自动设置size的数组就可以了。

 Notice:应该格外小心定义Required字段。当因为某原因要把Required字段改为Optional字段是,会有问题,老版本读取器会认为消息中没有该字段不完整,可能会拒绝或者丢弃该字段(Google文档是这么说的,但是我试了一下,将required的改为optional的,再用原来required时候的解析代码去读,如果字段赋值的话,并不会出错,但是如果字段未赋值,会报这样错误:Exception in thread “main” com.google.protobuf.InvalidProtocolBufferException: Message missing required fields:fieldname)。在设计时,尽量将这种验证放在应用程序端的完成。Google的一些工程师对此也很困惑,他们觉得,required类型坏处大于好处,应该尽量仅适用optional或者repeated的。但也并不是所有的人都这么想。

如果想深入学习.proto文件书写,可以参考Protocol Buffer Language Guide。但是不要妄想会有类似于类继承这样的机制,Protocol Buffers不做这个…

4. 编译Protocol Buffers

定义好.proto文件后,接下来,就是使用该文件,运行PB的编译器protoc,编译.proto文件,生成相关类,可以使用这些类读写“通讯簿”没得message。接下来我们要做:

  1. 如果你还没有安装PB编译器,到这里现在安装:download the package
  2. 安装后,运行protoc,结束后会发现在项目com.example.tutorial package下,生成了AddressBookProtos.java文件:
1
2
3
protoc -I=$SRC_DIR –java_out=$DST_DIR $SRC_DIR/addressbook.proto
#for example
protoc -I=G:\workspace\protobuf\message –java_out=G:\workspace\protobuf\src\main\java G:\workspace\protobuf\messages\addressbook.proto

  • -I:指明应用程序的源码位置,假如不赋值,则有当前路径(说实话,该处我是直译了,并不明白是什么意思。我做了尝试,该值不能为空,如果为空,则提示赋了一个空文件夹,如果是当前路径,请用.代替,我用.代替,又提示不对。但是可以是任何一个路径,都运行正确,只要不为空);
  • –java_out:指明目的路径,即生成代码输出路径。因为我们这里是基于java来说的,所以这里是–java_out,相对其他语言,设置为相对语言即可
  • 最后一个参数即.proto文件

Notice:此处运行完毕后,查看生成的代码,很有可能会出现一些类没有定义等错误,例如:com.google cannot be resolved to a type等。这是因为项目中缺少protocol buffers的相应library。在Protocol Buffers的源码包里,你会发现java/src/main/java,将这下边的文件拷贝到你的项目,大概可以解决问题。我只能说大概,因为当时我在弄得时候,也是刚学,各种出错,比较恶心。有一个简单的方法,呵呵,对于懒汉来说。创建一个maven的java项目,在pom.xml中,添加Protocol Buffers的依赖即可解决所有问题~在pom.xml中添加如下依赖(注意版本):

1
2
3
4
5
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>2.5.0</version>
</dependency>

 5. Protocol Buffer Java API

5.1 产生的类及方法

接下来看一下PB编译器创建了那些类以及方法。首先会发现一个.java文件,其内部定义了一个AddressBookProtos类,即我们在addressbook.proto文件java_outer_classname 指定的。该类内部有一系列内部类,对应分别是我们在addressbook.proto中定义的message。每个类内部都有相应的Builder类,我们可以用它创建类的实例。生成的类及类内部的Builder类,均自动生成了获取message中字段的方法,不同的是,生成的类仅有getter方法,而生成类内部的Builder既有getter方法,又有setter方法。本例中Person类,其仅有getter方法,如图所示:

 但是Person.Builder类,既有getter方法,又有setter方法,如图:

person.builderperson.builder

从上边两张图可以看到:

  1. 每一个字段都有JavaBean风格的getter和setter
  2. 对于每一个简单类型变量,还对应都有一个has这样的一个方法,如果该字段被赋值了,则返回true,否则,返回false
  3. 对每一个变量,都有一个clear方法,用于置空字段

对于repeated字段:

repeated filedrepeated filed

从图上看:

  1. 从person.builder图上看出,对于repeated字段,还有一个特殊的getter,即getPhoneCount方法,及repeated字段还有一个特殊的count方法
  2. 其getter和setter方法根据index获取或设置一个数据项
  3. add()方法用于附加一个数据项
  4. addAll()方法来直接增加一个容器中的所有数据项

注意到一点:所有的这些方法均命名均符合驼峰规则,即使在.proto文件中是小写的。PB compiler生成的方法及字段等都是按照驼峰规则来产生,以符合基本的Java规范,当然,其他语言也尽量如此。所以,在proto文件中,命名最好使用用“_”来分割不同小写的单词。

 5.2 枚举及嵌套类

从代码中可以发现,还产生了一个枚举:PhoneType,该枚举位于Person类内部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum PhoneType
        implements com.google.protobuf.ProtocolMessageEnum {
      /**
       * <code>MOBILE = 0;</code>
       */
      MOBILE(0,0),
      /**
       * <code>HOME = 1;</code>
       */
      HOME(1,1),
      /**
       * <code>WORK = 2;</code>
       */
      WORK(2,2),
      ;
      …
}

除此之外,如我们所预料,还有一个Person.PhoneNumber内部类,嵌套在Person类中,可以自行看一下生成代码,不再粘贴。

5.3 Builders vs. Messages

由PB compiler生成的消息类是不可变的。一旦一个消息对象构建出来,他就不再能够修改,就像java中的String一样。在构建一个message之前,首先要构建一个builder,然后使用builder的setter或者add()等方法为所需字段赋值,之后调用builder对象的build方法。

在使用中会发现,这些构造message对象的builder的方法,都又会返回一个新的builder,事实上,该builder跟调用这个方法的builder是同一方法。这样做的目的,仅是为了方便而已,我们可以把所有的setter写在一行内。

如下构造一个Person实例:

1
2
3
4
5
6
7
8
9
10
11
12
Person john = Person
        .newBuilder()
        .setId(1)
        .setName(“john”)
        .setEmail(“[email protected]”)
        .addPhone(
                PhoneNumber
                .newBuilder()
                .setNumber(“1861xxxxxxx”)
                .setType(PhoneType.WORK)
                .build()
        ).build();

5.4 标准消息方法

每一个消息类及Builder类,基本都包含一些公用方法,用来检查和维护这个message,包括:

  1.  isInitialized(): 检查是否所有的required字段是否被赋值
  2. toString(): 返回一个便于阅读的message表示(本来是二进制的,不可读),尤其在debug时候比较有用
  3. mergeFrom(Message other): 仅builder有此方法,将其message的内容与此message合并,覆盖简单及重复字段
  4. clear(): 仅builder有此方法,清空所有的字段

5.5 解析及序列化

对于每一个PB类,均提供了读写二进制数据的方法:

  1. byte[] toByteArray();: 序列化message并且返回一个原始字节类型的字节数组
  2. static Person parseFrom(byte[] data);: 将给定的字节数组解析为message
  3. void writeTo(OutputStream output);: 将序列化后的message写入到输出流
  4. static Person parseFrom(InputStream input);: 读入并且将输入流解析为一个message

这里仅列出了几个解析及序列化方法,完整列表,可以参见:Message API reference

6. 使用PB生成类写入

接下来使用这些生成的PB类,初始化一些联系人,并将其写入一个文件中。

下面的程序首先从一个文件中读取一个通讯簿(AddressBook),然后添加一个新的联系人,再将新的通讯簿写回到文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.example.tutorial;
 
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintStream;
 
class AddPerson {
    // This function fills in a Person message based on user input.
    static Person PromptForAddress(BufferedReader stdin, PrintStream stdout)
            throws IOException {
        Person.Builder person = Person.newBuilder();
 
        stdout.print(“Enter person ID: “);
        person.setId(Integer.valueOf(stdin.readLine()));
 
        stdout.print(“Enter name: “);
        person.setName(stdin.readLine());
 
        stdout.print(“Enter email address (blank for none): “);
        String email = stdin.readLine();
        if (email.length() >0) {
            person.setEmail(email);
        }
 
        while (true) {
            stdout.print(“Enter a phone number (or leave blank to finish): “);
            String number = stdin.readLine();
            if (number.length() ==0) {
                break;
            }
 
            Person.PhoneNumber.Builder phoneNumber = Person.PhoneNumber
                    .newBuilder().setNumber(number);
 
            stdout.print(“Is this a mobile, home, or work phone? “);
            String type = stdin.readLine();
            if (type.equals(“mobile”)) {
                phoneNumber.setType(Person.PhoneType.MOBILE);
            }else if (type.equals(“home”)) {
                phoneNumber.setType(Person.PhoneType.HOME);
            }else if (type.equals(“work”)) {
                phoneNumber.setType(Person.PhoneType.WORK);
            }else {
                stdout.println(“Unknown phone type.  Using default.”);
            }
 
            person.addPhone(phoneNumber);
        }
 
        return person.build();
    }
 
    // Main function: Reads the entire address book from a file,
    // adds one person based on user input, then writes it back out to the same
    // file.
    public static void main(String[] args)throws Exception {
        if (args.length !=1) {
            System.err.println(“Usage:  AddPerson ADDRESS_BOOK_FILE”);
            System.exit(-1);
        }
 
        AddressBook.Builder addressBook = AddressBook.newBuilder();
 
        // Read the existing address book.
        try {
            addressBook.mergeFrom(new FileInputStream(args[0]));
        }catch (FileNotFoundException e) {
            System.out.println(args[0]
                    +”: File not found.  Creating a new file.”);
        }
 
        // Add an address.
        addressBook.addPerson(PromptForAddress(new BufferedReader(
                new InputStreamReader(System.in)), System.out));
 
        // Write the new address book back to disk.
        FileOutputStream output =new FileOutputStream(args[0]);
        addressBook.build().writeTo(output);
        output.close();
    }
}

 7. 使用PB生成类读取

运行第六部分程序,写入几个联系人到文件中,接下来,我们就要读取联系人。程序入下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.example.tutorial;
import java.io.FileInputStream;
 
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
 
class ListPeople {
  // Iterates though all people in the AddressBook and prints info about them.
  static void Print(AddressBook addressBook) {
    for (Person person: addressBook.getPersonList()) {
      System.out.println(“Person ID: ” + person.getId());
      System.out.println(”  Name: ” + person.getName());
      if (person.hasEmail()) {
        System.out.println(”  E-mail address: ” + person.getEmail());
      }
 
      for (Person.PhoneNumber phoneNumber : person.getPhoneList()) {
        switch (phoneNumber.getType()) {
          case MOBILE:
            System.out.print(”  Mobile phone #: “);
            break;
          case HOME:
            System.out.print(”  Home phone #: “);
            break;
          case WORK:
            System.out.print(”  Work phone #: “);
            break;
        }
        System.out.println(phoneNumber.getNumber());
      }
    }
  }
 
  // Main function:  Reads the entire address book from a file and prints all
  //   the information inside.
  public static void main(String[] args)throws Exception {
    if (args.length !=1) {
      System.err.println(“Usage:  ListPeople ADDRESS_BOOK_FILE”);
      System.exit(-1);
    }
 
    // Read the existing address book.
    AddressBook addressBook =
      AddressBook.parseFrom(new FileInputStream(args[0]));
 
    Print(addressBook);
  }
}

至此我们已经可以使用生成类写入和读取PB message。

8. 拓展PB

当产品发布后,迟早有一天我们需要改善我们的PB定义。如果要做到新的PB能够向后兼容,同时老的PB又能够向前兼容,我们必须遵守如下规则:

  1. 千万不要修改现有字段后边的数值标签
  2. 千万不要增加或者删除required字段
  3. 可以删除optional或者repeated字段
  4. 可以添加新的optional或者repeated字段,但是必须使用新的数字标签(该数字标签必须从未在该PB中使用过,包括已经删除字段的数字标签)

如果违反了这些规则,会有一些相应的异常,可参见some exceptions,但是这些异常,很少很少会被用到。

遵守这些规则,老的代码可以正确的读取新的message,但是会忽略新的字段;对于删掉的optional的字段,老代码会使用他们的默认值;对于删除的repeated字段,则把他们置为空。

新的代码也将能够透明的读取老的messages。但是必须注意,新的optional字段在老的message中是不存在的,必须显式的使用has_方法来判断其是否设置了,或者在.proto 文件中以[default = value]形式提供默认值。如果没有指定默认值的话,会按照类型默认值赋值。对于string类型,默认值是空字符串。对于bool来说,默认值是false。对于数字类型,默认值是0。

9. 高级用法

Protocol Buffers的应用远远不止简单的存取以及序列化。如果想了解更多用法,可以去研究Java API reference

Protocol Message Class提供了一个重要特性:反射。不需要再写任何特殊的message类型就可以遍历一条message的所有字段以及操作字段的值。反射的一个非常重要的应用是可以将PBmessage与其他的编码语言进行转化,例如与XML或者JSON之间。

反射另外一个更加高级的应用应该是两个同一类型message的之间的不同,或者开发一种可以成为“Protocol Buffers 正则表达式”的应用,使用它,可以编写符合一定消息内容的表达式。

除此之外,开动脑筋,你会发现,Protocol Buffers能解决远远超过你刚开始对他的期待。

译自:https://developers.google.com/protocol-buffers/docs/javatutorial

X分钟学习clojure

 clojure, 开发  X分钟学习clojure已关闭评论
2月 172016
 

开始学习clojure,网上看到的一个快速入门学习的文章,推荐下,转自:https://segmentfault.com/a/1190000000414279

Clojure是运行在JVM上的Lisp家族中的一员。她比Common Lisp更强调纯函数式编程,且自发布时便包含了一组工具来处理状态。

这种组合让她能十分简单且自动地处理并发问题。

(你需要使用Clojure 1.2或更新的发行版)

	
; 注释以分号开始。

Clojure代码由一个个form组成, 即写在小括号里的由空格分开的一组语句。
Clojure解释器会把第一个元素当做一个函数或者宏来调用,其余的被认为是参数。

Clojure代码的第一条语句一般是用ns来指定当前的命名空间。

	
(ns learnclojure)

str会使用所有参数来创建一个字符串

	
(str "Hello" " " "World") ; => "Hello World"

数学计算比较直观

	
(+ 1 1) ; => 2 (- 2 1) ; => 1 (* 1 2) ; => 2 (/ 2 1) ; => 2

等号是 =

	
(= 1 1) ; => true (= 2 1) ; => false

逻辑非

	
(not true) ; => false

嵌套的form工作起来应该和你预想的一样

	
(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2

类型

Clojure使用Java的Object来描述布尔值、字符串和数字
用函数 class 来查看具体的类型

	
(class 1) ; 整形默认是java.lang.Long类型 (class 1.); 浮点默认是java.lang.Double类型的 (class ""); String是java.lang.String类型的,要用双引号引起来 (class false) ; 布尔值是java.lang.Boolean类型的 (class nil); "null"被称作nil

如果你想创建一组数据字面量,用单引号(‘)来阻止form被解析和求值

	
'(+ 1 2) ; => (+ 1 2)

单引号是quote的简写形式,故上式等价于(quote (+ 1 2))

可以对一个引用列表求值

	
(eval '(+ 1 2)) ; => 3

集合(Collection)和序列

List的底层实现是链表,Vector的底层实现是数组
二者也都是java类

	
(class [1 2 3]); => clojure.lang.PersistentVector (class '(1 2 3)); => clojure.lang.PersistentList

list本可以写成(1 2 3), 但必须用引用来避免被解释器当做函数来求值。
(list 1 2 3)等价于'(1 2 3)

集合其实就是一组数据
List和Vector都是集合:

	
(coll? '(1 2 3)) ; => true (coll? [1 2 3]) ; => true

序列 (seqs) 是数据列表的抽象描述
只有列表才可称作序列。

	
(seq? '(1 2 3)) ; => true (seq? [1 2 3]) ; => false

序列被访问时只需要提供一个值,所以序列可以被惰性加载——也就意味着可以定义一个无限序列:

	
(range 4) ; => (0 1 2 3) (range) ; => (0 1 2 3 4 ...) (无限序列) (take 4 (range)) ; (0 1 2 3)

cons用以向列表或向量的起始位置添加元素

	
(cons 4 [1 2 3]) ; => (4 1 2 3) (cons 4 '(1 2 3)) ; => (4 1 2 3)

conj将以最高效的方式向集合中添加元素。
对于列表,数据会在起始位置插入,而对于向量,则在末尾位置插入。

	
(conj [1 2 3] 4) ; => [1 2 3 4] (conj '(1 2 3) 4) ; => (4 1 2 3)

用concat来合并列表或向量

	
(concat [1 2] '(3 4)) ; => (1 2 3 4)

用filter来过滤集合中的元素,用map来根据指定的函数来映射得到一个新的集合

	
(map inc [1 2 3]) ; => (2 3 4) (filter even? [1 2 3]) ; => (2)

recuce使用函数来规约集合

	
(reduce + [1 2 3 4]) ; = (+ (+ (+ 1 2) 3) 4) ; => 10

reduce还能指定一个初始参数

	
(reduce conj [] '(3 2 1)) ; = (conj (conj (conj [] 3) 2) 1) ; => [3 2 1]

函数

用fn来创建函数。函数的返回值是最后一个表达式的值

	
(fn [] "Hello World") ; => fn

你需要再嵌套一组小括号来调用它

	
((fn [] "Hello World")) ; => "Hello World"

你可以用def来创建一个变量(var)

	
(def x 1) x ; => 1

将函数定义为一个变量(var)

	
(def hello-world (fn [] "Hello World")) (hello-world) ; => "Hello World"

你可用defn来简化函数的定义

	
(defn hello-world [] "Hello World")

中括号内的内容是函数的参数。

	
(defn hello [name] (str "Hello " name)) (hello "Steve") ; => "Hello Steve"

你还可以用这种简写的方式来创建函数:

	
(def hello2 #(str "Hello " %1)) (hello2 "Fanny") ; => "Hello Fanny"

函数也可以有多个参数列表。

	
(defn hello3 ([] "Hello World") ([name] (str "Hello " name))) (hello3 "Jake") ; => "Hello Jake" (hello3) ; => "Hello World"

可以定义变参函数,即把&后面的参数全部放入一个序列

	
(defn count-args [& args] (str "You passed " (count args) " args: " args)) (count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"

可以混用定参和变参(用&来界定)

	
(defn hello-count [name & args] (str "Hello " name ", you passed " (count args) " extra args")) (hello-count "Finn" 1 2 3) ; => "Hello Finn, you passed 3 extra args"

哈希表

基于hash的map和基于数组的map(即arraymap)实现了相同的接口,hashmap查询起来比较快,
但不保证元素的顺序。

	
(class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap (class (hash-map :a 1 :b 2 :c 3)) ; => clojure.lang.PersistentHashMap

arraymap在足够大的时候,大多数操作会将其自动转换成hashmap,
所以不用担心(对大的arraymap的查询性能)。

map支持很多类型的key,但推荐使用keyword类型
keyword类型和字符串类似,但做了一些优化。

	
(class :a) ; => clojure.lang.Keyword (def stringmap {"a" 1, "b" 2, "c" 3}) stringmap ; => {"a" 1, "b" 2, "c" 3} (def keymap {:a 1, :b 2, :c 3}) keymap ; => {:a 1, :c 3, :b 2}

顺便说一下,map里的逗号是可有可无的,作用只是提高map的可读性。

从map中查找元素就像把map名作为函数调用一样。

	
(stringmap "a") ; => 1 (keymap :a) ; => 1

可以把keyword写在前面来从map中查找元素。

	
(:b keymap) ; => 2

但不要试图用字符串类型的key来这么做。

	
("a" stringmap) ; => Exception: java.lang.String cannot be cast to clojure.lang.IFn

查找不存在的key会返回nil。

	
(stringmap "d") ; => nil

用assoc函数来向hashmap里添加元素

	
(def newkeymap (assoc keymap :d 4)) newkeymap ; => {:a 1, :b 2, :c 3, :d 4}

但是要记住的是clojure的数据类型是不可变的!

	
keymap ; => {:a 1, :b 2, :c 3}

用dissoc来移除元素

	
(dissoc keymap :a :b) ; => {:c 3}

集合(Set)

	
(class #{1 2 3}) ; => clojure.lang.PersistentHashSet (set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}

用conj新增元素

	
(conj #{1 2 3} 4) ; => #{1 2 3 4}

用disj移除元素

	
(disj #{1 2 3} 1) ; => #{2 3}

把集合当做函数调用来检查元素是否存在:

	
(#{1 2 3} 1) ; => 1 (#{1 2 3} 4) ; => nil

在clojure.sets模块下有很多相关函数。

常用的form

clojure里的逻辑控制结构都是用宏(macro)实现的,这在语法上看起来没什么不同。

	
(if false "a" "b") ; => "b" (if false "a") ; => nil

用let来创建临时的绑定变量。

	
(let [a 1 b 2] (> a b)) ; => false

用do将多个语句组合在一起依次执行

	
(do (print "Hello") "World") ; => "World" (prints "Hello")

函数定义里有一个隐式的do

	
(defn print-and-say-hello [name] (print "Saying hello to " name) (str "Hello " name)) (print-and-say-hello "Jeff") ;=> "Hello Jeff" (prints "Saying hello to Jeff")

let也是如此

	
(let [name "Urkel"] (print "Saying hello to " name) (str "Hello " name)) ; => "Hello Urkel" (prints "Saying hello to Urkel")

模块

用use来导入模块里的所有函数

	
(use 'clojure.set)

然后就可以使用set相关的函数了

	
(intersection #{1 2 3} #{2 3 4}) ; => #{2 3} (difference #{1 2 3} #{2 3 4}) ; => #{1}

你也可以从一个模块里导入一部分函数。

	
(use '[clojure.set :only [intersection]])

用require来导入一个模块

	
(require 'clojure.string)

用/来调用模块里的函数
下面是从模块clojure.string里调用blank?函数。

	
(clojure.string/blank? "") ; => true

在import里你可以给模块名指定一个较短的别名。

	
(require '[clojure.string :as str]) (str/replace "This is a test." #"[a-o]" str/upper-case) ; => "THIs Is A tEst."

#””用来表示一个正则表达式

你可以在一个namespace定义里用:require的方式来require(或use,但最好不要用)模块。
这样的话你无需引用模块列表。

	
(ns test (:require [clojure.string :as str] [clojure.set :as set]))

Java

Java有大量的优秀的库,你肯定想学会如何用clojure来使用这些Java库。

用import来导入java类

	
(import java.util.Date)

也可以在ns定义里导入

	
(ns test (:import java.util.Date java.util.Calendar))

用类名末尾加.的方式来new一个Java对象

	
(Date.) ; <a date object>

用.操作符来调用方法,或者用.method的简化方式。

	
(. (Date.) getTime) ; <a timestamp> (.getTime (Date.)) ; 和上例一样。

用/调用静态方法

	
(System/currentTimeMillis) ; <a timestamp> (system is always present)

用doto来更方便的使用(可变)类。

	
(import java.util.Calendar) (doto (Calendar/getInstance) (.set 2000 1 1 0 0 0) .getTime) ; => A Date. set to 2000-01-01 00:00:00

STM

软件内存事务(Software Transactional Memory)被clojure用来处理持久化的状态。
clojure里内置了一些结构来使用STM。
atom是最简单的。给它传一个初始值

	
(def my-atom (atom {}))

用swap!更新atom。
swap!会以atom的当前值为第一个参数来调用一个指定的函数,
swap其余的参数作为该函数的第二个参数。

	
(swap! my-atom assoc :a 1) ; Sets my-atom to the result of (assoc {} :a 1) (swap! my-atom assoc :b 2) ; Sets my-atom to the result of (assoc {:a 1} :b 2)

用@读取atom的值

	
my-atom ;=> Atom<#...> (返回Atom对象) @my-atom ; => {:a 1 :b 2}

下例是一个使用atom实现的简单计数器

	
(def counter (atom 0)) (defn inc-counter [] (swap! counter inc)) (inc-counter) (inc-counter) (inc-counter) (inc-counter) (inc-counter) @counter ; => 5

其他STM相关的结构是ref和agent.
; Refs: http://clojure.org/refs
; Agents: http://clojure.org/agents

进阶读物

本文肯定不足以讲述关于clojure的一切,但是希望足以让你迈出第一步。

Clojure.org官网有很多文章:
http://clojure.org/

Clojuredocs.org有大多数核心函数的文档,还带了示例哦:
http://clojuredocs.org/quickref/Clojure%20Core

4Clojure是个很赞的用来练习clojure/FP技能的地方:
http://www.4clojure.com/

Clojure-doc.org 有很多入门级的文章:
http://clojure-doc.org/

8月 072015
 

一篇很好的lucence入门他文章,推荐下,转自:http://blog.csdn.net/chenghui0317/article/details/10052103

一、Lucene的介绍

     Lucene是一个全文检索的框架,apache组织提供了一个用java实现的全文搜索引擎的开源项目,其功能非常的强大,api非常简单,并且有了全文检索的功能支持可以非常方便的实现根据关键字来搜索整个应用系统的内容,大大提高了用户的体验效果。   使用Lucene来建立、搜索功能和操作数据库有一点像,这样就可想而知,Lucene使用起来还是蛮方便的。

    那么为什么要使用Lucene 呢? 因为如果没有使用Lucene ,那么就要根据某个关键字来搜索数据库表记录, 就要使用like 一个字符一个字符去匹配,这样子查询的方式的要累坏程序员不说,并且查询数据库的性能开销可想而知。

二、Lucene的执行流程

    前面说了Lucene的操作方式和操作数据库有点相似,所以如果要使用Lucene 就要先创建“数据库”,然后往这个“数据表”中一行一行的插入数据,数据插入成功之后,就可以操作这张“数据表”,实现增删改查操作了。

   总的来说,可以这样理解:

  1、创建一个索引文件目录,然后把需要检索的信息 用Field 对应匹配的 封装成一个Document文档对象,将这个对象放入索引文件目录中,这里既可以将索引存放到磁盘中,也可以放入内存中,如果放入内存,那么程序关闭索引就没有了,所以一般都是将索引放入磁盘中;

  2、如果发现信息有问题需要删除,那么索引文件也要删除,否则检索的时候还是会查询得到,这个时候需要根据索引id去删除对应的索引;

  3、如果发现信息被更新了,那么索引文件也要更新,这个时候需要先将旧的索引删除然后添加新的索引;

  4、最后重头戏是全文搜索了,这和查询数据库一样,先需要创建索引读取对象,然后封装Query查询对象,调用search()方法 得到检索结果。

三、使用Lucene的准备条件

   lucene-core-3.6.0.jar

   lucene-highlighter-3.6.0.jar

   lucene-memory-3.6.0.jar

下载地址:http://download.csdn.net/detail/ch656409110/5971413


四、使用Lucene实战

1、使用Lucene将索引 写入内存

实现的思路如下:

   <1>创建了内存目录对象RAMDirectory 和 索引写入器IndexWriter  ;

   <2>利用索引写入器将指定的数据存入内存目录对象中;

   <3>创建IndexSearch 索引查询对象,然后根据关键字封装Query查询对象;

   <4>调用search()方法,将查询的结果返回给TopDocs ,迭代里面所有的Document对象,显示查询结果;

   <5>关闭IndexWriter 写入器,关闭RAMDirectory目录对象。

具体代码如下:

  1. package com.lucene.test;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.lucene.analysis.SimpleAnalyzer;  
  6. import org.apache.lucene.document.Document;  
  7. import org.apache.lucene.document.Field;  
  8. import org.apache.lucene.index.IndexWriter;  
  9. import org.apache.lucene.index.Term;  
  10. import org.apache.lucene.search.IndexSearcher;  
  11. import org.apache.lucene.search.Query;  
  12. import org.apache.lucene.search.TermQuery;  
  13. import org.apache.lucene.search.TopDocs;  
  14. import org.apache.lucene.store.RAMDirectory;  
  15.   
  16. /** 
  17.  * lucene 检索内存索引 非常简单的例子 
  18.  *  
  19.  * @author Administrator 
  20.  *  
  21.  */  
  22. public class RAMDirectoryDemo {  
  23.     public static void main(String[] args) throws IOException {  
  24.         long startTime = System.currentTimeMillis();  
  25.         System.out.println(“*****************检索开始**********************”);  
  26.         // 创建一个内存目录对象,所以这里生成的索引不会放在磁盘中,而是在内存中。  
  27.         RAMDirectory directory = new RAMDirectory();  
  28.         /* 
  29.          * 创建索引写入对象,该对象既可以把索引写入到磁盘中也可以写入到内存中。 参数说明:  
  30.          * public IndexWriter(Directory d, Analyzer a, boolean create, MaxFieldLength mfl) 
  31.          * directory:目录对象,也可以是FSDirectory 磁盘目录对象  
  32.          * analyzer:分词器,分词器就是将检索的关键字分割成一组组词组, 它是lucene检索查询的一大特色之一, new SimpleAnalyzer()这个是lucene自带的最为简单的分词器; create: 是否新建,这里肯定要设为true; 
  33.          * maxFieldLength:这个是分词器拆分最大长度,因为各种不同类型的分词器拆分的字符颗粒细化程度不一样,所以需要设置一个最长的拆分长度。IndexWriter.MaxFieldLength.UNLIMITED表示无限制; 
  34.          */  
  35.         IndexWriter writer = new IndexWriter(directory, new SimpleAnalyzer(),true, IndexWriter.MaxFieldLength.UNLIMITED);  
  36.         // 创建Document 文档对象,在lucene中创建的索引可以看成数据库中的一张表,表中也可以有字段,往里面添加内容之后可以根据字段去匹配查询  
  37.         // 下面创建的doc对象中添加了三个字段,分别为name,sex,dosomething,  
  38.         Document doc = new Document();  
  39.         /* 
  40.         * 参数说明 public Field(String name, String value, Store store, Index index)  
  41.         * name : 字段名称  
  42.         * value : 字段的值 store : 
  43.         *  Field.Store.YES:存储字段值(未分词前的字段值) Field.Store.NO:不存储,存储与索引没有关系 
  44.         *  Field.Store.COMPRESS:压缩存储,用于长文本或二进制,但性能受损  
  45.         * index : 建立索引的方式,是否建立分词等等 
  46.         *  Field.Index.ANALYZED:分词建索引 
  47.         *  Field.Index.ANALYZED_NO_NORMS:分词建索引,但是Field的值不像通常那样被保存,而是只取一个byte,这样节约存储空间  
  48.         *  Field.Index.NOT_ANALYZED:不分词且索引 ,一旦指定为这种类型后将会被lucenn录入索引中,但不会被作为关键搜索,除非输入所有的关键字 
  49.         *  Field.Index.NOT_ANALYZED_NO_NORMS:不分词建索引,Field的值去一个byte保存 
  50.         */  
  51.         doc.add(new Field(“name”“Chenghui”, Field.Store.YES,Field.Index.ANALYZED));  
  52.         doc.add(new Field(“sex”“男的”, Field.Store.YES,Field.Index.NOT_ANALYZED));  
  53.         doc.add(new Field(“dosometing”“I am learning lucene “,Field.Store.YES, Field.Index.ANALYZED));  
  54.         writer.addDocument(doc);  
  55.         writer.close(); // 这里可以提前关闭,因为dictory 写入内存之后 与IndexWriter 没有任何关系了  
  56.   
  57.         // 因为索引放在内存中,所以存放进去之后要立马测试,否则,关闭应用程序之后就检索不到了  
  58.         // 创建IndexSearcher 检索索引的对象,里面要传递上面写入的内存目录对象directory  
  59.         IndexSearcher searcher = new IndexSearcher(directory);  
  60.         // 根据搜索关键字 封装一个term组合对象,然后封装成Query查询对象  
  61.         // dosometing是上面定义的字段,lucene是检索的关键字  
  62.          Query query = new TermQuery(new Term(“dosometing”“lucene”));  
  63.         // Query query = new TermQuery(new Term(“sex”, “男”));  
  64.         // Query query = new TermQuery(new Term(“name”, “cheng”));   
  65.           
  66.         // 去索引目录中查询,返回的是TopDocs对象,里面存放的就是上面放的document文档对象  
  67.         TopDocs rs = searcher.search(query, null10);  
  68.         long endTime = System.currentTimeMillis();  
  69.         System.out.println(“总共花费” + (endTime – startTime) + “毫秒,检索到” + rs.totalHits + “条记录。”);  
  70.         for (int i = 0; i < rs.scoreDocs.length; i++) {  
  71.             // rs.scoreDocs[i].doc 是获取索引中的标志位id, 从0开始记录  
  72.             Document firstHit = searcher.doc(rs.scoreDocs[i].doc);  
  73.             System.out.println(“name:” + firstHit.getField(“name”).stringValue());  
  74.             System.out.println(“sex:” + firstHit.getField(“sex”).stringValue());  
  75.             System.out.println(“dosomething:” + firstHit.getField(“dosometing”).stringValue());  
  76.         }  
  77.         writer.close();  
  78.         directory.close();  
  79.         System.out.println(“*****************检索结束**********************”);  
  80.     }  
  81. }  

运行结果如下:

由上可知: 上面根据“lucene”关键字查询成功了,返回的是一个个Document封装的对象。

另外 如果在创建索引写入器IndexWriter的时候 create 参数指定为false的话 ,会报错,找不到索引文件,因为每一次读取都是”现存现取“的模式,具体如下:

Exception in thread “main” org.apache.lucene.index.IndexNotFoundException: no segments* file found in [email protected] [email protected]b480: files: []

根据dosomething可以查询成功,同样根据sex字段 和name字段一样可以实现查询功能。

<1>如果把 Query query = new TermQuery(new Term(“sex”, “男”)); 取消注释 ,然后查询一下:

结果发现根本没有记录,这是因为在生成索引的时候指定的sex字段 是Field.Index.NOT_ANALYZED 类型的,所以Lucene没有为该字段建立索引,也就不能根据sex字段去查询。

<2>然后 将sex 字段改为Field.Index.ANALYZED类型的,再查询一下:

结果发现依然没有记录,为什么?

这是因为使用的分词器SimpleAnalyzer 没有那么智能化,它只会对关键字中包含空格的词组进行分词和匹配,简而言之:如果“中国”是搜索关键字,那么它只会匹配索引表中对应字段中包含“ 中国 ”这样的词组,而包含“中”或者“国”的汉字不会被匹配, 记住这里面前后都有空格分开。同样的,如果创建的索引中数据有“我是中国人” ,那么要根据“中国“这个关键字根本匹配不到,因为SimpleAnalyzer 这个分词器只会去匹配 以空格分开的词组,所以要想匹配成功,那么添加索引的数据应该改为“我是 中国 人”,这样才会被检索到。

所以所以,要想使用”男“这个关键字匹配成功,就需要在添加索引的时候 改为”男 的“才可以。

既然中文汉字 是这样,试试看英文字母 是否亦如此。

果不其然:

<1>如果录入name字段的索引为”chenghui“  关键字指定为“cheng”搜不到;

<2>如果录入name字段的索引为”cheng hui“  关键字指定为“cheng”可以搜到;

<3>如果录入name字段的索引为”cheng hui“  关键字指定为“Cheng”搜不到;

<4>如果录入name字段的索引为”Cheng hui“  关键字指定为“Cheng”搜不到;

<5>如果录入name字段的索引为”Cheng hui“  关键字指定为“cheng”可以搜到;


由此可见: 索引录入的时候会统一转换成小写,但是关键字 没有转换成小写去匹配,这才导致大小写匹配不到的情况。

所以英文与数字和中文一样 不是那么人性化,并且我检索的时候连字母的大小写都没有区分,同时 数字 也是一样子的问题,   可见Lucene对其搜索的不完善性。

有没有解决方案呢? 有,上面例子使用的是SimpleAnalyzer 分词器,这个分词器是一段一段话进行分,现在介绍另外一个分词器StandardAnalyzer分词器,标准分词拿来分中文和ChineseAnalyzer一样的效果。

把上述代码中传递的分词器对象换成 new StandardAnalyzer(Version.LUCENE_36) ,试试效果,发现 StandardAnalyzer 在 SimpleAnalyzer 的基础上进行了优化,遗憾的是只是中文方面的,比如:

<1>如果录入sex索引为”男的“  关键字指定为“男” 可以搜到;

但是仅仅这样,远远不能满足全文检索的需求,这就需要使用更加高级的分词器来实现该功能。

8月 292013
 

写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法。

这个入门篇分上下两篇。本文着重动手,用 maven 来构建运行 hellow world 程序,体会一下不用任何 IDE ,只用 maven 是咋回事。然后下篇就讲解一下 maven 的核心概念。写这两篇文章特意回避了复杂的示例,也不使用 IDE ,目的是排除干扰,着重于 maven 本身。

本文的源代码可从这里下载。  

Apache Maven 是做什么用的?

 

Maven 是一个项目管理和构建自动化工具。但是对于我们程序员来说,我们最关心的是它的项目构建功能。所以这里我们介绍的就是怎样用 maven 来满足我们项目的日常需要。

Maven 使用惯例优于配置的原则 。它要求在没有定制之前,所有的项目都有如下的结构:

 

目录

目的

${basedir}

存放 pom.xml和所有的子目录

${basedir}/src/main/java

项目的 java源代码

${basedir}/src/main/resources

项目的资源,比如说 property文件

${basedir}/src/test/java

项目的测试类,比如说 JUnit代码

${basedir}/src/test/resources

测试使用的资源

 

一个 maven 项目在默认情况下会产生 JAR 文件,另外 ,编译后 的 classes 会放在 ${basedir}/target/classes 下面, JAR 文件会放在 ${basedir}/target 下面。

这时有人会说了 , Ant 就没有那么多要求 ,它允许你可以自由的定义项目的结构。在这里不想引起口水战哈, 我个人觉得 maven 的这些默认定义很方便使用。 

好了 ,接下来我们来安装 maven 。

 

Maven 的安装

 

在安装 maven 前,先保证你安装了 JDK 。 JDK 6 可以从 Oracle 技术网上下载:

http://www.oracle.com/technetwork/cn/java/javase/downloads/index.html

Maven 官网的下载链接是 : http://maven.apache.org/download.html 。

该页的最后给出了安装指南。

 

安装完成后,在命令行运行下面的命令:  

   

 

$ mvn -v 

Apache Maven 3.0.3 (r1075438; 2011-03-01 01:31:09+0800)

Maven home: /home/limin/bin/maven3

Java version: 1.6.0_24, vendor: Sun Microsystems Inc.

Java home: /home/limin/bin/jdk1.6.0_24/jre

Default locale: en_US, platform encoding: UTF-8

OS name: "linux", version: "2.6.35-28-generic-pae", arch: "i386", family: "unix"





如果你看到类似上面的输出的话,就说明安装成功了。

接下来我们用 maven 来建立最著名的“Hello World!”程序 🙂

注意:如果你是第一次运行 maven,你需要 Internet 连接,因为 maven 需要从网上下载需要的插件。

我们要做的第一步是建立一个 maven 项目。在 maven 中,我们是执行 maven 目标 (goal) 来做事情的。

maven 目标和 ant 的 target 差不多。在命令行中执行下面的命令来建立我们的 hello world 项目

 

 

  ~$mvn archetype:generate -DgroupId=com.mycompany.helloworld -DartifactId=helloworld -Dpackage=com.mycompany.helloworld -Dversion=1.0-SNAPSHOT



archetype:generate 目标会列出一系列的 archetype 让你选择。 Archetype 可以理解成项目的模型。 Maven 为我们提供了很多种的项目模型,包括从简单的 Swing 到复杂的 Web 应用。我们选择默认的 maven-archetype-quickstart ,是编号 #106 ,如下图所示:

 

apache maven image1

 

 连打两个回车,这时候让你确定项目属性的配置,

 

apache maven image2

  

这些属性是我们在命令行中用 -D 选项指定的。该选项使用 -Dname=value 的格式。回车确认,就完成了项目的建立,如下图所示:

 

apache maven image3

  

这时候我们看一下 maven 给我们建立的文件目录结构:

apache maven image4





maven 的 archetype 插件建立了一个 helloworld 目录,这个名字来自 artifactId 。在这个目录下面,有一个 Project Object Model(POM) 文件 pom.xml 。这个文件用于描述项目,配置插件和管理依赖关系。

源代码和资源文件放在 src/main 下面,而测试代码和资源放在 src/test 下面。

Maven 已经为我们建立了一个 App.java 文件:

   

Java代码

    package com.mycompany.helloworld;   
      
    /**  
     * Hello world!  
     *  
     */   
    public class App {   

      
        public static void main( String[] args ) {   
            System.out.println( "Hello World!" );   
        }   
    }  

 

 正是我们需要的 Hello World 代码。所以我们可以构建和运行这个程序了。用下面简单的命令构建:

 

~$cd helloworld

~$mvn package 

当你第一次运行 maven 的时候,它会从网上的 maven 库 (repository) 下载需要的程序,存放在你电脑的本地库 (local repository) 中,所以这个时候你需要有 Internet 连接。Maven 默认的本地库是 ~/.m2/repository/ ,在 Windows 下是 %USER_HOME%.m2repository 。

 apache maven image5



 如果构建没有错误的话,就会得到类似下面的结果:

 

apache maven image6

 

 这个时候, maven 在 helloworld 下面建立了一个新的目录 target/ ,构建打包后的 jar 文件 helloworld-1.0-SNAPSHOT.jar 就存放在这个目录下。编译后的 class 文件放在 target/classes/ 目录下面,测试 class 文件放在 target/test-classes/ 目录下面。

 为了验证我们的程序能运行,执行下面的命令:

 ~$java -cp target/helloworld-1.0-SNAPSHOT.jar com.mycompany.helloworld.App

 

apache maven image8

 

运行成功!!

 

现在你可能会有不少的问题。所以下一篇文章会解释 maven 的核心概念,希望能回答你可能会有的一些疑问。

 

接下来我们介绍下面这几个核心概念:

  • POM (Project Object Model)
  • Maven 插件
  • Maven 生命周期
  • Maven 依赖管理
  • Maven 库

 

POM (Project Object Model)

 

一个项目所有的配置都放置在 POM 文件中:定义项目的类型、名字,管理依赖关系,定制插件的行为等等。比如说,你可以配置 compiler 插件让它使用 java 1.5 来编译。

现在看一下第一篇文章中示例的 POM

Xml 代码

        <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

     <modelVersion>4.0.0</modelVersion> 



     <groupId>com.mycompany.helloworld</groupId> 

     <artifactId>helloworld</artifactId> 

     <version>1.0-SNAPSHOT</version> 

     <packaging>jar</packaging> 



     <name>helloworld</name> 

     <url>http://maven.apache.org</url> 



     <properties> 

       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 

     </properties> 



     <dependencies>

       <dependency> 

         <groupId>junit</groupId> 

         <artifactId>junit</artifactId> 

         <version>3.8.1</version> 

         <scope>test</scope> 

       </dependency> 

     </dependencies> 

    </project>    

 

在 POM 中,groupId, artifactId, packaging, version 叫作 maven 坐标,它能唯一的确定一个项目。有了 maven 坐标,我们就可以用它来指定我们的项目所依赖的其他项目,插件,或者父项目。一般 maven 坐标写成如下的格式:

    	groupId:artifactId:packaging:version

像我们的例子就会写成:

    	com.mycompany.helloworld: helloworld: jar: 1.0-SNAPSHOT

我们的 helloworld 示例很简单,但是大项目一般会分成几个子项目。在这种情况下,每个子项目就会有自己的 POM 文件,然后它们会有一个共同的父项目。这样只要构建父项目就能够构建所有的子项目了。子项目的 POM 会继承父项目的 POM。另外,所有的 POM都继承了一个 Super-POM。Super-POM 设置了一些默认值,比如在第一篇文章中提到的默认的目录结构,默认的插件等等,它遵循了惯例优于配置的原则。所以尽管我们的这个 POM 很简单,但是这只是你看得见的一部分。运行时候的 POM 要复杂的多。 如果你想看到运行时候的 POM 的全部内容的话,可以运行下面的命令:

$mvn help:effective-pom

Maven 插件

 

第一篇文章中,我们用了 mvn archetype:generate 命令来生成一个项目。那么这里的 archetype:generate 是什么意思呢?archetype 是一个插件的名字,generate是目标(goal)的名字。这个命令的意思是告诉 maven 执行 archetype 插件的 generate 目标。插件目标通常会写成pluginId:goalId

一个目标是一个工作单元,而插件则是一个或者多个目标的集合。比如说Jar插件,Compiler插件,Surefire插件等。从看名字就能知道,Jar 插件包含建立Jar文件的目标, Compiler 插件包含编译源代码和单元测试代码的目标。Surefire 插件的话,则是运行单元测试。

看到这里,估计你能明白了,mvn 本身不会做太多的事情,它不知道怎么样编译或者怎么样打包。它把构建的任务交给插件去做。插件定义了常用的构建逻辑,能够被重复利用。这样做的好处是,一旦插件有了更新,那么所有的 maven 用户都能得到更新。

Maven 生命周期

 

上一篇文章中,我们用的第二个命令是:mvn package。这里的 package 是一个maven的生命周期阶段 (lifecycle phase )。生命周期指项目的构建过程,它包含了一系列的有序的阶段 (phase),而一个阶段就是构建过程中的一个步骤。

那么生命周期阶段和上面说的插件目标之间是什么关系呢?插件目标可以绑定到生命周期阶段上。一个生命周期阶段可以绑定多个插件目标。当 maven 在构建过程中逐步的通过每个阶段时,会执行该阶段所有的插件目标。

maven 能支持不同的生命周期,但是最常用的是默认的Maven生命周期 (default Maven lifecycle )。如果你没有对它进行任何的插件配置或者定制的话,那么上面的命令 mvn package 会依次执行默认生命周期中直到包括 package 阶段前的所有阶段的插件目标:

  1. process-resources 阶段:resources:resources

  2. compile 阶段:compiler:compile

  3. process-classes 阶段:(默认无目标)

  4. process-test-resources 阶段:resources:testResources

  5. test-compile 阶段:compiler:testCompile

  6. test 阶段:surefire:test

  7. prepare-package 阶段:(默认无目标)

  8. package 阶段:jar:jar

     

Maven 依赖管理

 

之前我们说过,maven 坐标能够确定一个项目。换句话说,我们可以用它来解决依赖关系。在 POM 中,依赖关系是在 dependencies 部分中定义的。在上面的 POM 例子中,我们用 dependencies 定义了对于 junit 的依赖:

Xml 代码

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

那这个例子很简单,但是实际开发中我们会有复杂得多的依赖关系,因为被依赖的 jar 文件会有自己的依赖关系。那么我们是不是需要把那些间接依赖的 jar 文件也都定义在POM中呢?答案是不需要,因为 maven 提供了传递依赖的特性。

所谓传递依赖是指 maven 会检查被依赖的 jar 文件,把它的依赖关系纳入最终解决的依赖关系链中。针对上面的 junit 依赖关系,如果你看一下 maven 的本地库(我们马上会解释 maven 库)~/.m2/repository/junit/junit/3.8.1/ ,



part2-1

你会发现 maven 不但下载了 junit-3.8.1.jar,还下载了它的 POM 文件。这样 maven 就能检查 junit 的依赖关系,把它所需要的依赖也包括进来。

在 POM 的 dependencies 部分中,scope 决定了依赖关系的适用范围。我们的例子中 junit 的 scope 是 test,那么它只会在执行 compiler:testCompile and surefire:test 目标的时候才会被加到 classpath 中,在执行 compiler:compile 目标时是拿不到 junit 的。

我们还可以指定 scope 为 provided,意思是 JDK 或者容器会提供所需的jar文件。比如说在做web应用开发的时候,我们在编译的时候需要 servlet API jar 文件,但是在打包的时候不需要把这个 jar 文件打在 WAR 中,因为servlet容器或者应用服务器会提供的。

scope 的默认值是 compile,即任何时候都会被包含在 classpath 中,在打包的时候也会被包括进去。

 

Maven 

 

当第一次运行 maven 命令的时候,你需要 Internet 连接,因为它要从网上下载一些文件。那么它从哪里下载呢?它是从 maven 默认的远程库(http://repo1.maven.org/maven2) 下载的。这个远程库有 maven 的核心插件和可供下载的 jar 文件。

但是不是所有的 jar 文件都是可以从默认的远程库下载的,比如说我们自己开发的项目。这个时候,有两个选择:要么在公司内部设置定制库,要么手动下载和安装所需的jar文件到本地库。

本地库是指 maven 下载了插件或者 jar 文件后存放在本地机器上的拷贝。在 Linux 上,它的位置在 ~/.m2/repository,在 Windows XP 上,在 C:Documents and Settingsusername.m2repository ,在 Windows 7 上,在 C:Usersusername.m2repository。当 maven 查找需要的 jar 文件时,它会先在本地库中寻找,只有在找不到的情况下,才会去远程库中找。

运行下面的命令能把我们的 helloworld 项目安装到本地库:

     $mvn install

一旦一个项目被安装到了本地库后,你别的项目就可以通过 maven 坐标和这个项目建立依赖关系。比如如果我现在有一个新项目需要用到 helloworld,那么在运行了上面的 mvn install 命令后,我就可以如下所示来建立依赖关系:

Xml 代码

    <dependency>
      <groupId>com.mycompany.helloworld</groupId>
      <artifactId>helloworld</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>

好了,maven 的核心概念就简单的介绍到这里。至于在 Eclipse 中如何使用 maven,这个网上很多了,google 一下就行。

 

转自:http://www.oracle.com/technetwork/cn/community/java/apache-maven-getting-started-1-406235-zhs.html

 Posted by at 下午6:19  Tagged with: