自己备忘,随便写
android网络框架源码解析及对比
android常用网络框架对比
Volley:
特点
- 基于HttpUrlConnection
- 封装了UIL图片加载框架,支持图片加载
- 缓存
- Activity和生命周期的联动,Activity结束时取消在此Activity中调用了所有网络请求
应用场景
- 适合传输数据量小,网络请求频繁的场景
- 不能进行大数据量的网络操作,比如下载及上传文件,原因如下:
- Volley的Request和Response都是把数据放到byte[]中,如果设计文件的上传及下载,byte[]就会变得很大,严重的消耗内存.比如下载一个大文件,不可能把整个文件一次性全部放到byte[]中再写到本地文件.
- 源码为证:
Request: com.android.volley.Request //Reqest中的数据,最后是被转换为byte[]数组 //Returns the raw POST or PUT body to be sent. public byte[] getBody() throws AuthFailureError { Map
params = getParams(); if (params != null && params.size() > 0) { return encodeParameters(params, getParamsEncoding()); } return null; } //Request实例解析当前请求得到的网络相应数据 protected abstract Response parseNetworkResponse(NetworkResponse response); Response: com.android.volley.NetworkResponse //网络响应中的原始数据,以byte[]形式存在 //Raw data from this response. public final byte[] data; Request的具体继承类,都是在parseNetworkResponse方法中,对NetworkResponse的data(byte[])进行解析: StringRequest.parseNetworkResponse: parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); ImageRequest.parseNetworkResponse: byte[] data = response.data; BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); Bitmap bitmap = null; bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
OkHttp:
特点
- 基于NIO和Okio,请求处理速度更快.
IO:阻塞式;NIO:非阻塞式;Okio:Square基于IO和NIO做的更高效处理数据流的库
IO和NIO的区别: 1.IO是面向流(Stream)的,而NIO是面向缓冲区(Buffer)的. 1.1:面向流意味着IO读取流中1个或多个字节,他们没有被缓存在任何地方,此外它不能前后移动流中的数据; 1.2:面向缓冲区意味着先将数据读取到一个稍后处理的缓冲区,需要读取时可以在缓冲区中前后移动; 2:IO是阻塞的,NIO是非阻塞的 2.1:IO的各种流是阻塞的,意味着一个线程执行read()或write()时,该线程被阻塞,直到数据被读取或完全写入,期间不能做任何别的事情; 2.2:NIO,一个线程从1个通道读取数据,或者向1个通道写入数据,如果通道中暂时没有数据可以读取,或者写入数据没有完成,线程不会阻塞,可以去做别的事情.直到通道中出现了可以读取的数据或者可以继续写入数据,再继续之前的工作.NIO情况下,一个线程可以处理多个通道的读取和写入,更充分的利用线程资源; 3:IO和NIO的适用场景 3.1:IO适合于链接数量不大,但是每个链接需要发送/接收的数据量很大,需要长时间连续处理; 3.2:NIO更适合于同时存在海量链接,但是每个链接单次发送/接收的数据量较小的情形.比如聊天服务器.海量链接但是单个链接单次数据较小
- 无缝支持GZIP来减少数据流量
- GZIP是网站压缩加速的一种技术,开启后可以加快客户端的打开速度.原理是响应数据先经过服务器压缩,客户端快速解压呈现内容,减少客户端接收的数据量
- android客户端在Request头加入"Accept-Encoding","gzip",告知服务器客户端接受gzip的数据;服务器支持的情况下,返回gzip后的response body,同时加入以下header:
Content-Encoding: gzip:表明body是gzip过的数据 Content-Length:117:表示body gzip压缩后的数据大小,便于客户端使用。 或 Transfer-Encoding: chunked:分块传输编码
- OkHttp3是支持Gzip解压缩的:它支持我们在发起请求的时候自动加入header,Accept-Encoding:gzip,而我们的服务器返回的时候也需要header中有Content-Encoding:gzip
开发者没有在Header中添加Accept-Encoding时,自动添加Accept-Encoding: gzip 自动添加的request,response支持自动解压 手动添加不负责解压缩 自动解压时移除Content-Length,所以上层Java代码想要contentLength时为-1 自动解压时移除 Content-Encoding 自动解压时的分块编码传输不受影响 okhttp3.internal.http.BridgeInterceptor public final class BridgeInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { **** //如果header中没有Accept-Encoding,默认自动添加,且标记变量transparentGzip为true boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } **** Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest); //符合条件时执行gzip自动解压: //header中手动添加ccept-Encoding不负责gzip解压 //自动添加ccept-Encoding才负责gzip解压 if (transparentGzip&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))&&HttpHeaders.hasBody(networkResponse)) { //gzip自动解压的前提条件 //1:transparentGzip = true,即用户没有主动在request头中加入"Accept-Encoding", "gzip" //2:header中标明了Content-Encoding为gzip //3:networkResponse中有body GzipSource responseBody = new GzipSource(networkResponse.body().source()); Headers strippedHeaders = networkResponse.headers().newBuilder() //自动解压时移除Content-Encoding .removeAll("Content-Encoding") //自动解压时移除Content-Length,所以上层Java代码想要contentLength时为-1 .removeAll("Content-Length") .build(); responseBuilder.headers(strippedHeaders); responseBuilder.body(new RealResponseBody(strippedHeaders,Okio.buffer(responseBody))); } return responseBuilder.build(); } }
- 使用OkHttp3,我们在向服务器提交大量数据,希望对post的数据进行gzip压缩的实现方法:首先实现自定义拦截器,然后在构建OkhttpClient的时候,添加拦截器
实现自定义拦截器(官方实现): static class GzipRequestInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) { return chain.proceed(originalRequest); } Request compressedRequest = originalRequest.newBuilder() .header("Content-Encoding", "gzip") .method(originalRequest.method(), gzip(originalRequest.body())) .build(); return chain.proceed(compressedRequest); } private RequestBody gzip(final RequestBody body) { return new RequestBody() { @Override public MediaType contentType() { return body.contentType(); } @Override public long contentLength() { //因为无法预知在经过gzip压缩后的长度,设置为-1 return -1; } @Override public void writeTo(BufferedSink sink) throws IOException { //通过GzipSink,将原始的BufferedSink进行gzip压缩 BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); //将经过gzip压缩的内容写入RequestBody body.writeTo(gzipSink); gzipSink.close(); } }; } } 构建OkhttpClient的时候,添加拦截器: OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new GzipRequestInterceptor())//开启Gzip压缩 ... .build();
应用场景
- 重量级网络交互场景:网络请求频繁,传输数据量大
Retrofit:
特点
- 基于OkHttp
- 通过注解配置请求
- 性能最好,处理最快
- 解析数据需要使用统一的Converter
- 易与其他框架RxJava联合使用
应用场景
- 任何场景下都优先使用,特别是项目中有使用RxJava或者后台API遵循Restful风格
Retrofit的使用
Retrofit涉及到的注解
- 网络请求方法注解:
@GET,@POST,@PUT,@OPTIONS,@PATCH,@DELETE,@HEAD,@HTTP @HTTP用于替换其余方法注解,通过method,path,hasBody进行设置: public @interface HTTP { //网络请求的方法(区分大小写) String method(); //网络请求地址路径 String path() default ""; //是否有请求体 boolean hasBody() default false; } 实例: @HTTP(method="GET",path="blog/{id}",hasBody=false) Call
getCall(@Path("id") int id); 网络请求的完整Url=创建Retrofit实例时通过.baseUrl()+方法注解(path), 通常使用:baseUrl目录形式+path相对路径 的方法组成完整Url: Url = "http:host:port/a/b/appath" baseUrl = "http:host:port/a/b/" path = appath - 标记注解
@FormUrlEncoded,@Multipart,@Streaming @FormUrlEncoded:表示发送form-encoded的数据 每个键值对需要用@Filed来注解键名,随后的对象提供值 @Multipart:表示发送form-encoded的数据(适用于有文件上传的场景) 1:每个键值对需要用@Part来注解键名,随后的对象提供值. 2:@Part后面支持3种数据类型:RequestBody,okhttp3.MultipartBody.Part,任意类型 @Streaming:表示返回数据以流的形式返回,适用于返回数据较大的场景.如果没有使用Streaming,默认把数据全部载入内存,之后读取数据也是从内存中获取. 实例: public interface GetRequest_Interface { /** *表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded) * Field("username")表示将后面的String name中name的取值作为username 的值 */ @POST("/form") @FormUrlEncoded Call
testFormUrlEncoded1(@Field("username") String name, @Field("age") int age); @POST("/form") @Multipart Call testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file); } 具体使用: GetRequest_Interface service = retrofit.create(GetRequest_Interface.class); // @FormUrlEncoded Call call1 = service.testFormUrlEncoded1("Carson", 24); // @Multipart // 1:Part修饰的参数类型:RequestBody MediaType textType = MediaType.parse("text/plain"); RequestBody name = RequestBody.create(textType, "Carson"); RequestBody age = RequestBody.create(textType, "24"); // 2:Part修饰的参数类型:MultipartBody.Part //2.1:文件路径 String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath()+ File.separator + "icon.jpg"; //2.2:文件 File file = new File(path); //2.3:文件所关联的MediaType MediaType type = MediaType.parse("image/*"); //2.4:通过MediaType和File创建RequestBody RequestBody body = RequestBody.create(type,file); //2.5:通过MultipartBody.Part.createFormData(String name, @Nullable String filename, RequestBody body) // 创建指定文件关联的MultipartBody.Part实例 MultipartBody.Part filePart = MultipartBody.Part.createFormData("image", "icon.jpg", body); Call call3 = service.testFileUpload1(name, age, filePart); - MultipartBody.Part.createFormData(String name, @Nullable String filename, RequestBody body):
- name是网络请求中的名称
- fileName是文件名称,用于服务端解析
- body就是文件关联的RequestBody实例
- 每个RequestBody都要指定MediaType,常见的MediaType.parse(X)中X:
text/plain(纯文本) application/x-www-form-urlencoded(使用HTTP的POST方法提交的表单) multipart/form-data(同上,但主要用于表单提交时伴随文件上传的场合 image/gif(GIF图像) image/jpeg(JPEG图像)【PHP中为:image/pjpeg】 image/png(PNG图像)【PHP中为:image/x-png】 video/mpeg(MPEG动画) application/octet-stream(任意的二进制数据) application/pdf(PDF文档) application/msword(Microsoft Word文件)
- 详情的文件扩展名和X之间的对应关系:MIME 参考手册
- 如果任意一个文件/File,不知道其对应的MediaType type=MediaType.parse(X)中X填什么,通过以下代码可以获取X值:
通过文件完整路径,来获取其对应的X import java.net.FileNameMap; import java.net.URLConnection; public class FileUtils { public static String getMimeType(String fileUrl) throws java.io.IOException { FileNameMap fileNameMap = URLConnection.getFileNameMap(); String type = fileNameMap.getContentTypeFor(fileUrl); if (contentType == null) { //* exe,所有的可执行程序 contentType = "application/octet-stream"; } return type; } } 1:文件的完整路径 String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "MyDirectoty" + File.separator + "test.png"; String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "MyDirectoty" + File.separator + "2.doc"; String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "MyDirectoty" + File.separator + "2.csv"; String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "MyDirectoty" + File.separator + "LiveUpdate.exe"; String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "MyDirectoty" + File.separator + "1.txt"; String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "MyDirectoty" + File.separator + "demo.jpg"; String mimeType = FileUtils.getMimeType(filePath); //image/png //application/msword //application/vnd.ms-excel //application/x-msdownload //text/plain //image/jpeg
- MultipartBody.Part.createFormData(String name, @Nullable String filename, RequestBody body):
- 网络请求参数注解
- @Headers:用于添加固定的请求头,注解在方法上
实例: @Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-Sample-App" }) @GET("users/{username}") Call
getUser(@Path("username") String username); @Headers("Cache-Control: max-age=640000") @GET("widget/list") Call - > widgetList();
- @Header:用于添加不固定的请求头,注解在方法参数上
实例: @GET("user") Call
getUser(@Header("Authorization") String authorization) - @HeaderMap:用于添加请求头集合,注解在方法参数上
实例: Map
headers = new HashMap()<>; headers.put("Accept","text/plain"); headers.put("Accept-Charset", "utf-8"); @GET("/search") void list(@HeaderMap Map headers); - @Body:以 Post方式 传递 自定义数据类型 给服务器
- @Body注解参数,则不能同时使用@FormUrlEncoded、@Multipart,否则会报错:
@Body parameters cannot be used with form or multi-part encoding
- @Body是以什么形式上传的参数:是上传的@Body参数实体的Json字符串,所以内部需要一个GsonCoverter来将实体转换成json字符串,需要Retrofit里配置addConverterFactory(GsonConverterFactory.create()).否则会报错:
Unable to create @Body converter for ***
- @Body使用正确姿势
//1:配置你的Gson Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd hh:mm:ss") .create(); //2:Retrofit实例设置addConverterFactory(GsonConverterFactory.create()) Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") //可以接收自定义的Gson,当然也可以不传 .addConverterFactory(GsonConverterFactory.create(gson)) .build(); //3:@Body注解的参数,所在方法去掉@FormUrlEncoded、@Multipart public interface BlogService { @POST("blog") Call
> createBlog(@Body Blog blog); } //4:调用 BlogService service = retrofit.create(BlogService.class); Blog blog = new Blog(); blog.content = "新建的Blog"; blog.title = "测试"; blog.author = "怪盗kidou"; Call > call = service.createBlog(blog);
- @Body注解参数,则不能同时使用@FormUrlEncoded、@Multipart,否则会报错:
- @Field,@FieldMap:发送 Post请求时提交请求的表单字段,需要和@FormUrlEncoded配合使用
实例: public interface GetRequest_Interface { @POST("/form") @FormUrlEncoded Call
testFormUrlEncoded1(@Field("username") String name, @Field("age") int age); @POST("/form") @FormUrlEncoded Call testFormUrlEncoded2(@FieldMap Map map); } // @Field Call call1 = service.testFormUrlEncoded1("Carson", 24); // @FieldMap Map map = new HashMap<>(); map.put("username", "Carson"); map.put("age", 24); Call call2 = service.testFormUrlEncoded2(map);。 - @Part,@PartMap:发送 Post请求 时提交请求的表单字段,需要和@Multipart配合使用.适用于文件上传场景.
- @Part注解的参数类型:RequestBody,okhttp3.MultipartBody.Part,任意类型
- @PartMap注解一个Map
实例: public interface GetRequest_Interface { @POST("/form") @Multipart Call
testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file); @POST("/form") @Multipart Call testFileUpload2(@PartMap Map args, @Part MultipartBody.Part file); @POST("/form") @Multipart Call testFileUpload3(@PartMap Map args); } // 具体使用 MediaType textType = MediaType.parse("text/plain"); RequestBody name = RequestBody.create(textType, "Carson"); RequestBody age = RequestBody.create(textType, "24"); RequestBody file = RequestBody.create(MediaType.parse("multipart/form-data"), File一个文件); // @Part MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file); Call call3 = service.testFileUpload1(name, age, filePart); // @PartMap // 实现和上面同样的效果 Map fileUpload2Args = new HashMap<>(); fileUpload2Args.put("name", name); fileUpload2Args.put("age", age); //这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有 //fileUpload2Args.put("file", file); Call call4 = service.testFileUpload2(fileUpload2Args, filePart); - @Query,@QueryMap:用于 @GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value)
- @Query:URL问号后面的参数;
- @QueryMap:相当于多个@Query
- @Query和@QueryMap注解的查询参数的key和value默认都会开启URL编码,使用如下encoded=true来关闭URL编码.
- @Query(value="group",encoded=true)
- @QueryMap(encoded=true)
- @Query注解的参数,参数值可以为空,为空该参数会被忽略
- @QueryMap注解的Map,其键和值都不能为空,否则抛出IllegalArgumentException异常
实例: @Query: @GET("/list") Call
list(@Query("category") String category); //传入一个数组 @GET("/list") Call list(@Query("category") String... categories); //不进行URL编码 @GET("/search") Call llist(@Query(value="foo", encoded=true) String foo); @Query调用: X.list("1") URL:/list?category=1 X.list(null) URL:/list X.list("a","b") URL:/list?category=a&category=b @Query(value="foo", encoded=true)下,生成的URL和不设置encoded=true情况无差别,只是关闭了key和value的URL编码 @QueryMap: @GET("/search") Call list(@QueryMap Map filters); @GET("/search") Call list(@QueryMap(encoded=true) Map filters); @QueryMap调用: X.list(ImmutableMap.of("group", "coworker", "age", "42")) URL:/search?roup=coworker&age=42 X.list(ImmutableMap.of("group", "coworker")) URL:/search?roup=coworker - @Path:URL中"?"前面部分,Path注解用于替换url路径中的参数
实例: @GET("users/{user}/repos") Call
getBlog(@Path("user") String user ); X.getBlog("bb") URL:users/bb/repos - @Url:作用于方法参数,直接设置请求的接口地址
- 当@GET,@POST等注解里面没有url地址时,必须在方法中使用@Url,将地址以第1个参数的形式传入
- @Url注解的地址,不要以/开头
- @Url支持的类型有 okhttp3.HttpUrl, String, java.net.URI, android.net.Uri
- @Path注解与@Url注解不能同时使用,否则会抛异常
Path注解用于替换url路径中的参数,这就要求在使用path注解时, 必须已经存在请求路径,不然没法替换路径中指定的参数啊, 而Url注解是在参数中指定的请求路径的,这个时候指定请求路径已经晚了, path注解找不到请求路径,更别提更换请求路径中的参数了
public interface BlogService { /** * 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供 * 对于Query和QueryMap,如果不是String(或Map的第二个泛型参数不是String)时 * 会被默认会调用toString转换成String类型 * Url支持的类型有 okhttp3.HttpUrl, String, java.net.URI, android.net.Uri * {@link retrofit2.http.QueryMap} 用法和{@link retrofit2.http.FieldMap} 用法一样,不再说明 */ @GET //当有URL注解时,这里的URL就省略了 Call
testUrlAndQuery(@Url String url,@Query("showAll") boolean showAll); } Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") .build(); BlogService service = retrofit.create(BlogService.class); Call call1 = service.testUrlAndQuery("headers",false); //http://localhost:4567/headers?showAll=false
Retrofit使用流程
- 步骤1:添加Retrofit库的依赖
- Retrofit支持多种数据解析方式,使用时需要在build.gradle添加依赖
build.gradle添加依赖: compile 'com.squareup.retrofit2:retrofit:2.0.2' Gson com.squareup.retrofit2:converter-gson:2.0.2 Jackson com.squareup.retrofit2:converter-jackson:2.0.2 Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2 Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2 Moshi com.squareup.retrofit2:converter-moshi:2.0.2 Wire com.squareup.retrofit2:converter-wire:2.0.2 Scalars com.squareup.retrofit2:converter-scalars:2.0.2
- Retrofit支持多种网络请求适配器方式:guava、Java8和rxjava
build.gradle添加依赖: guava com.squareup.retrofit2:adapter-guava:2.0.2 Java8 com.squareup.retrofit2:adapter-java8:2.0.2 rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2
- Retrofit支持多种数据解析方式,使用时需要在build.gradle添加依赖
- 步骤2:创建 接收服务器返回数据 的类
- 步骤3:创建 用于描述网络请求 的接口
- 步骤4:创建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://fanyi.youdao.com/") // 设置网络请求的Url地址 .addConverterFactory(GsonConverterFactory.create())//设置数据解析器 .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//支持RxJava平台 .build();
- 步骤5:创建 网络请求接口实例 并 配置网络请求参数
// 创建 网络请求接口 的实例 GetRequest_Interface request = retrofit.create(GetRequest_Interface.class); //对 发送请求 进行封装 Call
call = request.getCall(); - 步骤6:发送网络请求(异步 / 同步)
//发送网络请求(异步) call.enqueue(new Callback
() { //请求成功时回调 @Override public void onResponse(Call call,Response response) { // 对返回数据进行处理 response.body().show(); } //请求失败时候的回调 @Override public void onFailure(Call call, Throwable throwable) { System.out.println("连接失败"); } }); // 发送网络请求(同步) Response response = call.execute(); // 对返回数据进行处理 response.body().show();
Retrofit源码分析
Retrofit涉及到的设计模式
- 模板模式
- 定义:定义一个操作的算法框架,将一些步骤延迟到子类中,使子类不改变算法的结构即可重新定义该算法的某些特定步骤
- 使用场景:多个子类有公有方法,且子类公有方法的调度逻辑基本相同
- 模板模式包含2个角色:
- 父类:抽象类 public abstract class AbsParent
- 父类中包含:基本方法 + 模板方法 + 钩子方法
- 基本方法:父类提取公共代码:protected abstract 方法,由子类具体实现
- 模板方法:可以有1个或几个,完成对基本方法的调度,实现具体逻辑:public final方法,防止子类复写
- 钩子方法:protected方法,注意不是抽象方法.在父类的模板方法中调用,对模板方法的执行进行约束
- 子类:父类的实现类 public class Child1 extends AbsParent
- 子类中包含:基本方法的具体实现 + 钩子方法的重写
- 父类:抽象类 public abstract class AbsParent
- 代码实例
//父类 public abstract class AbsParent{ //钩子方法 protected boolean executeStep1(){ return false; } //基本方法 protected abstract void step1(); protected abstract void step2(); protected abstract void step3(); //模板方法 public final void execute(){ if(this.executeStep1()){ this.step1(); } this.step2(); this.step3(); } } //子类 public class Child1 extends AbsParent{ //子类钩子方法返回值 private boolean executeFlag = true; //子类中可以对钩子方法返回值进行设置,从而对父类的模板方法进行约束 public void setExecuteFlag(boolean flag){ this.executeFlag = flag; } @Override protected boolean executeStep1(){ return this.executeFlag; } @Override protected void step1(){ System.out.println("Child1:step1") } @Override protected void step2(){ System.out.println("Child1:step2") } @Override protected void step3(){ System.out.println("Child1:step3") } }
- Builder模式/建造者模式
- 定义:将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示
- 作用:用户不需要知道复杂对象的建造过程细节,只需要指定对象的具体类型,即可创建复杂对象;具体建造者根据用户指定的对象具体类型A,按照指定顺序创建A的实例;
- 建造者模式包含4个角色:
- 产品类
- 产品类实现了模板模式.抽象产品父类包含基本方法和模板方法,具体产品子类实现了基本方法.
- 抽象建造者
- public abstract class:规范了产品的组建,全是抽象方法,是具体建造者的父类
- 具体建造者
- 实现抽象建造者所有方法,并返回一个建造好的具体产品子类实例
- 导演类
- 持有多个具体建造者,包含多个方法,用来生产多个具体产品类实例;
- 产品类
- 代码实例
抽象产品父类:包含基本方法和模板方法 public abstract class AbsProduct{ //这个参数定义了各基本方法的执行顺序 private ArrayList
sequence = new ArrayList (); //基本方法 protected abstract void step1(); protected abstract void step2(); //设置参数 public final void setSequence(ArrayList sequence){ this.sequence = sequence; } //模板方法 public final void do(){ for(String item:sequence){ if(item.equalsIgnoreCase("step1")){ this.step1(); }else if(item.equalsIgnoreCase("step2")){ this.step2(); } } } } 具体产品子类:实现了基本方法 public class Product1 extends AbsProduct{ @Override protected void step1(){ System.out.println("Product1:step1"); } @Override protected void step2(){ System.out.println("Product1:step2"); } } public class Product2 extends AbsProduct{ @Override protected void step1(){ System.out.println("Product2:step1"); } @Override protected void step2(){ System.out.println("Product2:step2"); } } 抽象建造者:规范产品的组建 public abstract class ProductBuilder{ //设置产品参数 public abstract void setSequence(ArrayList sequence); //获取产品实例 public abstract AbsProduct getProduct(); } 具体建造者:实现抽象建造者所有方法,并返回一个建造好的具体产品子类实例 public class Product1Builder extends ProductBuilder{ //私有变量就是将要产生的具体产品类实例 private Product1 p = new Product1(); @Override public void setSequence(ArrayList sequence){ this.p.setSequence(sequence); } @Override public AbsProduct getProduct(){ return this.p; } } public class Product2Builder extends ProductBuilder{ //私有变量就是将要产生的具体产品类实例 private Product2 p = new Product2(); @Override public void setSequence(ArrayList sequence){ this.p.setSequence(sequence); } @Override public AbsProduct getProduct(){ return this.p; } } 导演类:持有多个具体建造者,包含多个方法,用来生产多个具体产品类实例 public class Director{ //影响产品流程顺序的参数 private ArrayList sequence = new ArrayList (); //具体建造者 private Product1Builder builder1 = new Product1Builder(); private Product2Builder builder2 = new Product2Builder(); //根据需求可自行扩展 //1:生产不同类型的具体产品 //(gainProduct1A,gainProduct1B) 和 gainProduct2 就是生产不同类型的具体产品 //2:相同类型的产品,其产品流程的数量及顺序也可以任意变化: //gainProduct1A和gainProduct1B 就是同类产品的流程数量及顺序变化 public Product1 gainProduct1A(){ this.sequence.clear(); this.sequence.add("step1"); this.sequence.add("step2"); this.builder1.setSequence(this.sequence); return (Product1)this.builder1.getProduct(); } public Product1 gainProduct1B(){ this.sequence.clear(); this.sequence.add("step2"); this.builder1.setSequence(this.sequence); return (Product1)this.builder1.getProduct(); } public Product2 gainProduct2(){ this.sequence.clear(); this.sequence.add("step1"); this.sequence.add("step2"); this.builder2.setSequence(this.sequence); return (Product2)this.builder2.getProduct(); } }
- 外观模式/门面模式
- 定义:定义一个统一接口,外部与通过该统一的接口对子系统里的其他接口进行访问:如Retrofit调用某个具体接口,都是通过Retrofit.create创建的统一Service接口实例I,通过I调用具体方法;
- 实现:在复杂系统S和客户端C之间再加一层"接待员"R,在R中实现对S复杂功能的访问封装;C直接和R交互即可.
- 作用:隐藏了S的复杂性;R对S中复杂功能进行了封装,C调用R封装过的方法,可避免低水平错误
- 场景:去医院看病,要 挂号,问诊,缴费,取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便
- 外观模式包含:接口+实现类+外观类(R)
- 代码实例
//创建1个接口,代表医院每个流程 public interface Step{ void execute(); } //创建实现类,代表不同类型的具体流程 public class GuaHao implements Step{ @Override public void execute(){ System.out.println("老子正在挂号"); } } public class WenZhen implements Step{ @Override public void execute(){ System.out.println("老子正在问诊"); } } public class JiaoFei implements Step{ @Override public void execute(){ System.out.println("老子正在缴费"); } } public class QuYao implements Step{ @Override public void execute(){ System.out.println("老子正在取药"); } } //创建外观类"接待员R" public class Reception{ //"接待员"持有S中复杂功能的引用 private Step guahao; private Step wenzhen; private Step jiaofei; private Step quyao; public Reception(){ this.guahao = new GuaHao(); this.wenzhen = new WenZhen(); this.jiaofei = new JiaoFei(); this.quyao = new QuYao(); } //定义一个供客户端调用的方法,完整实现一串流程 public void executeAll(){ this.guahao.execute(); this.wenzhen.execute(); this.jiaofei.execute(); this.quyao.execute(); } //也可以封装单个流程 public void guaHao(){ this.guahao.execute(); }**** } //客户端直接调用Reception Reception r = new Reception(); r.executeAll(); r.guaHao();
- 代理模式
- 定义:通过访问代理类的方式来间接访问目标类
- 优点:隐藏目标类实现细节;不改变目标类情况下,对指定操作前后执行扩展,比如进行校验和其他操作
- 分类:
- 静态代理:代理类在程序运行前已经存在
- 动态代理:代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理
- 静态代理包含:
- 目标类和代理类共同实现的接口
- 目标类,代理类(代理类中持有目标类实例)
- 静态代理实例:
//创建一个接口 public interface MyOpt{ void opt(); } //创建目标类 public class TargetOpt implements MyOpt{ @Override public void opt(){ System.out.println("TargetOpt:opt"); } } //创建代理类 public class ProxyOpt implements MyOpt{ //代理类中持有 目标类实例 private TargetOpt target; public ProxyOpt(){ this.target = new TargetOpt(); } @Override public void opt(){ this.target.opt(); } } //客户端和代理类直接进行交互: ProxyOpt proxy= new ProxyOpt(); proxy.opt();
- 动态代理
- 动态代理对象P执行方法调用顺序:
- P.func==>InvocationHandler.invoke==>目标类实例.func
- 动态代理实现需要3步:
- 1 创建目标类接口 及 目标类
- 2 实现InvocationHandler接口
- 调用代理对象的每个函数实际最终都是调用了InvocationHandler的invoke函数
- 3 通过Proxy类新建代理类对象:Proxy.newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h)
- 动态代理实例:
//创建接口 public interface Step{ void execute(); } //创建目标类 public class MyStep implements Step{ @Override public void execute(){ System.out.println("MyStep:execute"); } } //实现InvocationHandler接口 public StepHandler implements InvocationHandler{ //target:目标类实例 private Object target; public StepHandler(){} public StepHandler(Object obj){ this.target = obj; } //proxy:通过 Proxy.newProxyInstance() 生成的代理对象 //method:表示proxy被调用的方法 //args:表示proxy被调用的方法的参数 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object obj = method.invoke(this.target, args); return obj; } } //通过Proxy类新建代理对象,直接调用代理对象的方法 //1:创建InvocationHandler的实现类实例,将目标类实例作为构造参数传入 StepHandler h = new StepHandler(new MyStep()); //2:创建代理对象 Proxy: Object newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h) loader:目标类继承的接口所属的类加载器 interfaces:目标类继承的接口的Class h:InvocationHandler的实现类实例 Step step = (Step)(Proxy.newProxyInstance(Step.class.getClassLoader(),new Class[]{Step.class},h)); //3:直接调用代理对象的方法 step.execute(); ==> "MyStep:execute" step.execute()实质是调用了生成的代理对象P中的execute方法 ==> 而P中的execute方法,是调用了刚刚创建的h.invoke方法 ==> h.invoke,则调用了目标类MyStep实例中的execute方法
- Proxy.newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h)源码分析
仅贴出关键代码 Proxy: package java.lang.reflect public class Proxy implements java.io.Serializable { private static final WeakCache
[], Class>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); private static final Class>[] constructorParams = { InvocationHandler.class }; protected InvocationHandler h; protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } @CallerSensitive public static Object newProxyInstance( ClassLoader loader, Class>[] interfaces, InvocationHandler h ) throws IllegalArgumentException { //获取目标类继承的接口的Class副本 final Class>[] intfs = interfaces.clone(); //1:通过接口的Class副本,获取代理类的Class Class> cl = getProxyClass0(loader, intfs); //2:Invoke its constructor with the designated invocation handler. //使用指定的InvocationHandler实例作为参数,执行代理类的构造函数,获取代理类实例 try { //2.1:Class.getConstructor //2.2:constructorParams //代理类extends Proxy, //cons实质上是Proxy的构造函数:protected Proxy(InvocationHandler h) //所以生成的代理类实例,内部的 h 就是 newProxyInstance方法传入的h final Constructor> cons = cl.getConstructor(constructorParams); **** return cons.newInstance(new Object[]{h}); } } //1:如果代理类已经存在则返回副本;不存在则通过ProxyClassFactory创建并返回:见1.1 private static Class> getProxyClass0(ClassLoader loader, Class>... interfaces) { // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); } //1.1:此处直接看ProxyClassFactory的apply方法即可 proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); private static final class ProxyClassFactory implements BiFunction [], Class>>{ //所有要生成的代理类名称前缀 // prefix for all proxy class names private static final String proxyClassNamePrefix = "$Proxy"; //为了生成的代理类不重名采取的名称后缀:后面代码会看到 // next number to use for generation of unique proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong(); //生成代理类的Class @Override public Class> apply(ClassLoader loader, Class>[] interfaces) { //接口数组生成Map Map , Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); //遍历代理类要实现的每个接口,注意必须是Interface,否则会抛异常 for (Class> intf : interfaces) { Class> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } //如果newProxyInstance中传入的ClassLoader并不是接口所属的ClassLoader,会抛异常 if (interfaceClass != intf) { throw new IllegalArgumentException(intf + " is not visible from class loader"); } //如果newProxyInstance中传入的Class数组,数组项不属于Interface,抛异常 if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } //通过Set,防止同一个接口重复处理 if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; //要生成的代理类所在package int accessFlags = Modifier.PUBLIC | Modifier.FINAL; //遍历每个接口,获取代理类所在package for (Class> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; //获取接口名称 //如:接口IStep,接口所在package为a.b.c, //则(new IStep()).getClass().getName为: "a.b.c.IStep" String name = intf.getName(); int n = name.lastIndexOf('.'); //获取接口所在package:对应IStep则为"a.b.c." String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { //proxyPkg不存在则赋值 proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { //注意:如果proxyPkg已经存在,说明传入的接口Class数组不止一个Clas项 //为了如果数组中的接口包名不同,就会抛异常 //所以 //1:保证所有Class关联的Interface包名相同 //2:只传1个Interface的Class不就行了,有需要传多个的场景吗? throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } **** //这里用到了AtomicLong,用来拼接代理类的名称 long num = nextUniqueNumber.getAndIncrement(); //以接口"a.b.c.IStep"为例,其代理类名称为:"a.b.c.$Proxy0" String proxyName = proxyPkg + proxyClassNamePrefix + num; //1.2:生成代理类class,以byte[]形式返回 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); try { //1.3:native方法生成代理类的Class,并返回 return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { **** } } } //1.3:defineClass0是一个native方法 private static native Class> defineClass0( ClassLoader loader, String name,byte[] b, int off, int len); //2.2:parameter types of a proxy class constructor //生成Proxy实例所需的InvocationHandler类 private static final Class>[] constructorParams = { InvocationHandler.class }; } //2.1:Class.getConstructor Class //返回值一个类符合指定参数数组的构造器 public Constructor getConstructor(Class>... parameterTypes) //1.2:ProxyGenerator.generateProxyClass ProxyGenerator package sun.misc public class ProxyGenerator { //代理类名称 private String className; //代理类继承的接口数组 private Class>[] interfaces; //访问修饰符:Modifier.PUBLIC|Modifier.FINAL private int accessFlags; private ProxyGenerator(String var1, Class>[] var2, int var3) { this.className = var1; this.interfaces = var2; this.accessFlags = var3; } //1.2 public static byte[] generateProxyClass(final String var0, Class>[] var1, int var2) { //设置代理类名称className //代理类继承的接口数组interfaces //访问修饰符accessFlags ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); //1.2.1:generateClassFile生成了代理类Class的byte[] final byte[] var4 = var3.generateClassFile(); *** return var4; } //1.2.1:生成代理类Class的byte[] //Object中三个方法:hashCode,equals,toString private static Method hashCodeMethod; private static Method equalsMethod; private static Method toStringMethod; static { try { hashCodeMethod = Object.class.getMethod("hashCode"); equalsMethod = Object.class.getMethod("equals", Object.class); toStringMethod = Object.class.getMethod("toString"); } catch (NoSuchMethodException var1) { throw new NoSuchMethodError(var1.getMessage()); } } private List fields = new ArrayList(); private List methods = new ArrayList(); private byte[] generateClassFile() { //addProxyMethod:1.2.1.1 //将Object中3个方法对应的ProxyGenerator.ProxyMethod实例添加到proxyMethods中 this.addProxyMethod(hashCodeMethod, Object.class); this.addProxyMethod(equalsMethod, Object.class); this.addProxyMethod(toStringMethod, Object.class); //代理类继承的接口数组,只考虑一个接口的场景 Class[] var1 = this.interfaces; //var2 = 1 int var2 = var1.length; int var3; Class var4; //只考虑一个接口的场景 for(var3 = 0; var3 < var2; ++var3) { var4 = var1[var3]; //获取接口中的public方法; //接口中的方法,默认就是public,所有获取到的是接口中所有方法 Method[] var5 = var4.getMethods(); int var6 = var5.length; //遍历接口中所有方法, //将其包装为ProxyGenerator.ProxyMethod实例添加到proxyMethods中 for(int var7 = 0; var7 < var6; ++var7) { Method var8 = var5[var7]; this.addProxyMethod(var8, var4); } } //proxyMethods的value的迭代器 Iterator var11 = this.proxyMethods.values().iterator(); **** Iterator var15; try { //methods中添加(?没看懂) this.methods.add(this.generateConstructor()); var11 = this.proxyMethods.values().iterator(); while(var11.hasNext()) { var12 = (List)var11.next(); var15 = var12.iterator(); while(var15.hasNext()) { //遍历proxyMethods中所有的ProxyGenerator.ProxyMethod //生成FieldInfo添加到fields //生成MethodInfo添加到methods ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next(); this.fields.add(new ProxyGenerator.FieldInfo( var16.methodFieldName, "Ljava/lang/reflect/Method;", 10)); this.methods.add(var16.generateMethod()); } } //methods中添加(?没看懂) this.methods.add(this.generateStaticInitializer()); }catch (IOException var10) {****} ****下面这段关键代码没看懂,只能猜一下**** //设置代理类访问修饰符 public final var14.writeShort(this.accessFlags); //设置代理类名称 var14.writeShort(this.cp.getClass(dotToSlash(this.className))); //设置代理类extends Proxy var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy")); **** for(int var19 = 0; var19 < var18; ++var19) { Class var22 = var17[var19]; //设置代理类implements接口数组中所有接口 var14.writeShort(this.cp.getClass(dotToSlash(var22.getName()))); } var14.writeShort(this.fields.size()); var15 = this.fields.iterator(); while(var15.hasNext()) { ProxyGenerator.FieldInfo var20 =(ProxyGenerator.FieldInfo)var15.next(); //遍历fields,在代理类中生成对应的变量. //变量数量X=继承的接口中方法数量+3(hashCode,equals,toString 3个) var20.write(var14); } var14.writeShort(this.methods.size()); var15 = this.methods.iterator(); while(var15.hasNext()) { ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next(); //遍历methods,在代理类中生成对应的方法. //方法数量Y= //3+继承的接口中的方法 //+this.generateConstructor()对应的构造方法 //+this.generateStaticInitializer()对应的static初始化方法快 //方法数量Y=X+2个(将静态方法快也看作1个方法) var21.write(var14); } var14.writeShort(0); //返回代理类Class生成的byte[] return var13.toByteArray(); } //1.2.1.1: private Map > proxyMethods = new HashMap(); private void addProxyMethod(Method var1, Class> var2) { String var3 = var1.getName(); //方法名称 Class[] var4 = var1.getParameterTypes(); //方法参数类型数组 Class var5 = var1.getReturnType(); //方法的正式返回类型 Class[] var6 = var1.getExceptionTypes(); //方法抛出的异常类型数组 //方法名称+拼接的方法参数字符串K,类似:"funcName(int a,int b,String c)" //1.2.1.1.1:获取 拼接的方法参数字符串 String var7 = var3 + getParameterDescriptors(var4); Object var8 = (List)this.proxyMethods.get(var7); if (var8 != null) { **** }else{ //创建ArrayList,以K为键,将ArrayList存储到proxyMethods中 var8 = new ArrayList(3); this.proxyMethods.put(var7, var8); } //向ArrayList中添加ProxyGenerator.ProxyMethod实例 //ProxyGenerator.ProxyMethod:一个和Method类似的包含代理类方法各属性的类 ((List)var8).add(new ProxyGenerator.ProxyMethod(var3, var4, var5, var6, var2, null)); } //1.2.1.1.1:根据方法参数类型数组,获取 拼接的方法参数字符串, //类似: "(int a,int b,String c)" private static String getParameterDescriptors(Class>[] var0) { StringBuilder var1 = new StringBuilder("("); for(int var2 = 0; var2 < var0.length; ++var2) { var1.append(getFieldType(var0[var2])); } var1.append(')'); return var1.toString(); } } - 动态代理生成的Class图示:
图片来自于公共技术点之 Java 动态代理
- 动态代理对象P执行方法调用顺序:
- 策略模式:简单讲就是替代if else if,根据参数选择对应算法处理
- 定义:定义一组算法,将它们封装起来,彼此可以替换;是if else if的优化
- 场景:在一个处理过程中,涉及很多if else判断,对同样的数据进行不同逻辑的处理;可以将每个具体的逻辑处理进行封装.根据具体情况选择合适的封装过的实例处理.
- 作用:解决复杂逻辑下if else太复杂难以维护.
- 成员:策略接口+策略实现类+环境角色+客户端
- 代码实例
场景:比如去洗头房,去过一次和去过100次, 洗头房给提供的技师水平是不同的(新手,老手,大师), 不同级别的技师,洗头的时间和手法都不同. //1:创建策略接口 public interface JiShi{ public void service(); } //2:创建具体策略角色 public class XinShou implements JiShi{ @Override public void service(){ System.out.println("洗头时间30分钟"); System.out.println("手法一般!"); } } public class LaoShou implements JiShi{ @Override public void service(){ System.out.println("洗头时间60分钟"); System.out.println("手法很好!"); } } public class DaShi implements JiShi{ @Override public void service(){ System.out.println("洗头时间90分钟"); System.out.println("手法牛逼!"); } } //3:创建环境角色 public class JiShiContext{ //环境角色中持有策略角色 private JiShi ji; //times:客人第几次来 public void xitou(int times){ if(times > 60){ ji = new DaShi(); }else if(times > 30){ ji = new LaoShou(); }else{ ji = new XinShou(); } ji.service(); } } //4:客户端调用 JiShiContext context = new JiShiContext(); context.xitou(70); context.xitou(40); context.xitou(10);