使用教程:

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1016/3588.html

retrofit2 与okhttp关系

http://blog.csdn.net/lmj623565791/article/details/51304204

DEMO,用api调用天气数据

http://blog.csdn.net/a553181867/article/details/52093695

Retrofit2 源码解读

构造方法

Retrofit构造方法采用构建者模式构造,源码如下。

private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();//key为接口中定义方法,value为转换过后的方法

  public static final class Builder {
private Platform platform;//平台:安卓、java等
private okhttp3.Call.Factory callFactory; //okhttp的Call工厂类,自定义newCall将Request转为Call
private HttpUrl baseUrl;//okhttp中的类,保存解析过的url
private List<Converter.Factory> converterFactories = new ArrayList<>();//类型转换工厂列表。
private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();//CallAdapter工厂列表。
private Executor callbackExecutor;//回调线程池
private boolean validateEagerly;//急需验证?作用在于直接将所有方法加入前面的map缓存中。 Builder(Platform platform) {
this.platform = platform;
converterFactories.add(new BuiltInConverters());//添加默认的转换器
} public Builder() {
this(Platform.get());//通过Platform.get()获取关于当前平台的实现
} public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
} okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();//默认使用OkHttpClient
} Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {//默认使用平台默认回调线程池
callbackExecutor = platform.defaultCallbackExecutor();
} //将平台默认CallAdapter.Factory加入列表中
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); //将默认Converter.Factory加入列表中
List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories); return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}

可以看出,首选通过Platform.get()来获取平台实现然后添加了默认转换工厂。由于我们是Android平台,毋容置疑,接下来就去看看Andoird类的源码。

static class Android extends Platform {
//回调线程池
@Override public Executor defaultCallbackExecutor() {
return new MainThreadExecutor();//主线程
} @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
//ExecutorCallAdapterFactory继承CallAdapter.Factory,内部代理了原来的Call<T>,用于将Callback回调到指定线程中。
return new ExecutorCallAdapterFactory(callbackExecutor);
} static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) {
handler.post(r);//采用Hanlder#post回调到主线程
}
}
}

Andoird类的源码主要实现了两个方法,一个是实现了默认的回调线程池,用于在主线程中执行任务,另一个是实现了CallAdapter工厂,通过代理的方式,将执行结果回调到callbackExecutor中去执行。所以,我们只需将callbackExecutor赋值为MainThreadExecutor即可实现主线程间的回调。

BuiltInConverters继承于Converter.Factory,Converter.Factory中有三个方法:

  • public Converter<ResponseBody, ?> responseBodyConverter(xx)用于将ResponseBody转换为指定类型,通常用于对响应结果的类型转换。
  • public Converter<?, RequestBody> requestBodyConverter(xx)用于将指定类型转为RequestBody。一般用于将@Body,@Part,@PartMap转为RequestBody
  • public Converter<?, String> stringConverter用于将指定类型转为String,用于将@Field,@FieldMap,@Path,@Query,@Header等注解的参数类型转为String。

Retrofit提供的用于自定义的方法如下:

  • client(OkHttpClient) 用于自定义客户端
  • callFactory(okhttp3.Call.Factory factory)用于自定义Call工厂,重写newCall将Request转为Call。OkHttpClient就是实现了这个接口。
  • addConverterFactory添加类型转换工厂(Gson转换等)
  • addCallAdapterFactory添加CallAdapter代理工厂,用来代理原始的Call(RxJavaCallAdapter等)。
  • callbackExecutor自定义回调线程池,默认为主线程
  • validateEagerly是否继续验证,是就提前将所有方法转为ServiceMethod放入缓存中,而不是调用一个缓存一个
  • baseUrl用于定义基本链接,必须以”/”结尾

假如基本地址为http://example.com/api/,关于baseUrl与注解中路径的拼接问题如下:

注解中的路径 最终Url (baseUrl为http://example.com/api/
foo/bar/ http://example.com/api/foo/bar/
/foo/bar/ http://example.com/foo/bar/
https://github.com/square/retrofit/ https://github.com/square/retrofit/
//github.com/square/retrofit/ http://github.com/square/retrofit/

接下来看使用:

Retrofit retrofit = new Retrofit.Builder()
.baseUrl(httpurl)
.addConverterFactory(GsonConverterFactory.create())
.build();
IWeather iWeather = retrofit.create(IWeather.class);
Call<City> call = iWeather.getCity(API_KEY, "shenzhen");
call.enqueue(new Callback<City>() {
@Override
public void onResponse(Call<City> call, retrofit2.Response<City> response) {}
@Override
public void onFailure(Call<City> call, Throwable t)
{}
});

以这段代码为例

我给Retrofit对象传了一个IWeather接口的Class对象,怎么又返回一个IWeather对象呢?进入create方法一看,没几行代码,但是我觉得这几行代码就是Retrofit的精妙的地方:

/** Create an implementation of the API defined by the {@code service} interface. */
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});

create方法就是返回了一个Proxy.newProxyInstance动态代理对象。那么问题来了...

动态代理是个什么东西?

看Retrofit代码之前我知道Java动态代理是一个很重要的东西,比如在Spring框架里大量的用到,但是它有什么用呢?

Java动态代理就是给了程序员一种可能:当你要调用某个Class的方法前或后,插入你想要执行的代码

比如你要执行某个操作前,你必须要判断这个用户是否登录,或者你在付款前,你需要判断这个人的账户中存在这么多钱。这么简单的一句话,我相信可以把一个不懂技术的人也讲明白Java动态代理是什么东西了。

为什么要使用动态代理

你看上面代码,获取数据的代码就是这句:

Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");

上面api对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi接口的implements产生的对象,当api对象调用getAuthor方法时会被动态代理拦截,然后调用Proxy.newProxyInstance方法中的InvocationHandler对象,它的invoke方法会传入3个参数:

  • Object proxy: 代理对象,不关心这个
  • Method method:调用的方法,就是getAuthor方法
  • Object... args:方法的参数,就是"qinchao"

而Retrofit关心的就是method和它的参数args,接下去Retrofit就会用Java反射获取到getAuthor方法的注解信息,配合args参数,创建一个ServiceMethod对象

ServiceMethod就像是一个中央处理器,传入Retrofit对象和Method对象,调用各个接口和解析器,最终生成一个Request,包含api 的域名、path、http请求方法、请求头、是否有body、是否是multipart等等。最后返回一个Call对象,Retrofit2中Call接口的默认实现是OkHttpCall,它默认使用OkHttp3作为底层http请求client

使用Java动态代理的目的就要拦截被调用的Java方法,然后解析这个Java方法的注解,最后生成Request由OkHttp发送

3 Retrofit的源码分析

想要弄清楚Retrofit的细节,先来看一下Retrofit源码的组成:

  1. 一个retrofit2.http包,里面全部是定义HTTP请求的Java注解,比如GETPOSTPUTDELETEHeadersPathQuery等等
  2. 余下的retrofit2包中几个类和接口就是全部retrofit的代码了,代码真的很少,很简单,因为retrofit把网络请求这部分功能全部交给了OkHttp了

Retrofit接口

Retrofit的设计非常插件化而且轻量级,真的是非常高内聚而且低耦合,这个和它的接口设计有关。Retrofit中定义了4个接口:

Callback<T>

这个接口就是retrofit请求数据返回的接口,只有两个方法

  • void onResponse(Response<T> response);
  • void onFailure(Throwable t);

Converter<F, T>

这个接口主要的作用就是将HTTP返回的数据解析成Java对象,主要有Xml、Gson、protobuf等等,你可以在创建Retrofit对象时添加你需要使用的Converter实现(看上面创建Retrofit对象的代码)

Call<T>

这个接口主要的作用就是发送一个HTTP请求,Retrofit默认的实现是OkHttpCall<T>,你可以根据实际情况实现你自己的Call类,这个设计和Volley的HttpStack接口设计的思想非常相似,子类可以实现基于HttpClientHttpUrlConnetction的HTTP请求工具,这种设计非常的插件化,而且灵活

CallAdapter<T>

上面说到过,CallAdapter中属性只有responseType一个,还有一个<R> T adapt(Call<R> call)方法,这个接口的实现类也只有一个,DefaultCallAdapter。这个方法的主要作用就是将Call对象转换成另一个对象,可能是为了支持RxJava才设计这个类的吧

Retrofit的运行过程

上面讲到ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);代码返回了一个动态代理对象,而执行Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");代码时返回了一个OkHttpCall对象,拿到这个Call对象才能执行HTTP请求

上面api对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi接口的implements产生的对象,当api对象调用getAuthor方法时会被动态代理拦截,然后调用Proxy.newProxyInstance方法中的InvocationHandler对象, 创建一个ServiceMethod对象

ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);

loadServiceMethod的方法很简陋,直接传入了Method然后build(),那么这个build()又做了什么?

ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);//首先从map中取看看是否已经缓存过
if (result == null) {//否则构造ServiceMethod。
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);//缓存map中
}
}
return result;
} public Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();//赋值方法注解数组
this.parameterTypes = method.getGenericParameterTypes();//赋值参数类型数组
this.parameterAnnotationsArray = method.getParameterAnnotations();//赋值参数注解数组
} public ServiceMethod build() {
callAdapter = createCallAdapter();//创建CallAdapter,用来代理Call
responseType = callAdapter.responseType();//获取返回类型
//...
responseConverter = createResponseConverter();//创建ResponseConverter,用来转换ResponseBody为指定类型 for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);//遍历解析方法注解
}
//...
int parameterCount = parameterAnnotationsArray.length;//parameterAnnotationsArray为参数注解数组
parameterHandlers = new ParameterHandler<?>[parameterCount];//初始化ParameterHandler,用来处理参数相关
for (int p = 0; p < parameterCount; p++) {//遍历参数注解数组
Type parameterType = parameterTypes[p];//获取参数类型
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];//获取参数注解数组
//...
//通过注解和参数类型,解析并赋值到parameterHandlers中
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
} //... return new ServiceMethod<>(this);
}

创建ServiceMethod

刚才说到,ServiceMethod就像是一个中央处理器,具体来看一下创建这个ServiceMethod的过程是怎么样的

第一步,获取到上面说到的3个接口对象:

callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
responseConverter = createResponseConverter();

第二步,解析Method的注解,主要就是获取Http请求的方法,比如是GET还是POST还是其他形式,如果没有,程序就会报错,还会做一系列的检查,比如如果在方法上注解了@Multipart,但是Http请求方法是GET,同样也会报错。因此,在注解Java方法是需要严谨

for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
} if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}

parseMethodAnnotation用于遍历解析方法上的注解,比如请求方法,请求头之类的。

private void parseMethodAnnotation(Annotation annotation) {
//请求方法注解
if (annotation instanceof DELETE) {
parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
} else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} else if (annotation instanceof HEAD) {
parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
if (!Void.class.equals(responseType)) {
throw methodError("HEAD method must use Void as response type.");
}
} else if (annotation instanceof PATCH) {
parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
} else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} else if (annotation instanceof PUT) {
parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
} else if (annotation instanceof OPTIONS) {
parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
} else if (annotation instanceof HTTP) {
//自定义HTTP请求注解
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotation instanceof retrofit2.http.Headers) {
//请求头注解
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
if (headersToParse.length == 0) {
throw methodError("@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);//解析Header
} else if (annotation instanceof Multipart) {
//Multipart
if (isFormEncoded) {
throw methodError("Only one encoding annotation is allowed.");
}
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
//FormUrlEncoded
if (isMultipart) {
throw methodError("Only one encoding annotation is allowed.");
}
isFormEncoded = true;
}
}

从上面源码可以看出,使用parseHttpMethodAndPath这个方法用于解析请求方法注解和路径参数保存到Set中

private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
if (this.httpMethod != null) {
throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
this.httpMethod, httpMethod);
}
this.httpMethod = httpMethod;
this.hasBody = hasBody; if (value.isEmpty()) {
return;
} int question = value.indexOf('?');//查询参数开始的符号
if (question != -1 && question < value.length() - 1) {
//如果在查询参数中使用了{},则抛出异常。
String queryParams = value.substring(question + 1);
Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
if (queryParamMatcher.find()) {
throw methodError("URL query string \"%s\" must not have replace block. "
+ "For dynamic query parameters use @Query.", queryParams);
}
} //赋值相对链接
this.relativeUrl = value;
//解析{}路径参数保存到Set中
this.relativeUrlParamNames = parsePathParameters(value);
}

从源码可以发现,不允许在查询参数中使用{}进行占位,否则就会抛出异常,然后将请求方法中的注解值赋值给relativeUrl,通过parsePathParameters将{}路径参数保存到Set中。

第三步,比如上面api中带有一个参数{user},这是一个占位符,而真实的参数值在Java方法中传入,那么Retrofit会使用一个ParameterHandler来进行替换:

int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];

最后,ServiceMethod会做其他的检查,比如用了@FormUrlEncoded注解,那么方法参数中必须至少有一个@Field@FieldMap

执行Http请求

之前讲到,OkHttpCall是实现了Call接口的,并且是真正调用OkHttp3发送Http请求的类。OkHttp3发送一个Http请求需要一个Request对象,而这个Request对象就是从ServiceMethodtoRequest返回的

总的来说,OkHttpCall就是调用ServiceMethod获得一个可以执行的Request对象,然后等到Http请求返回后,再将response body传入ServiceMethod中,ServiceMethod就可以调用Converter接口将response body转成一个Java对象

结合上面说的就可以看出,ServiceMethod中几乎保存了一个api请求所有需要的数据,OkHttpCall需要从ServiceMethod中获得一个Request对象,然后得到response后,还需要传入ServiceMethodConverter转换成Java对象

你可能会觉得我只要发送一个HTTP请求,你要做这么多事情不会很“慢”吗?不会很浪费性能吗?

我觉得,首先现在手机处理器主频非常高了,解析这个接口可能就花1ms可能更少的时间(我没有测试过),面对一个HTTP本来就需要几百ms,甚至几千ms来说不值得一提;而且Retrofit会对解析过的请求进行缓存,就在Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();这个对象中

调用服务接口

以上介绍了retrofit.create(XX.class);用于创建服务方法接口的过程。现在该进行相关调用了。我们知道,创建服务方法会返回一个Call<XX>对象,通过Call<XX>可以进行相关异步同步调用。

@Override public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null"); okhttp3.Call call;//okhttp中的call对象
Throwable failure; synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true; call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
try {
call = rawCall = createRawCall();//创建原始Call,即okhttp中的Call对象
} catch (Throwable t) {
failure = creationFailure = t;
}
}
} if (failure != null) {
callback.onFailure(this, failure);
return;
} if (canceled) {
call.cancel();
} //通过enqueue进行异步调用
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
throws IOException {
Response<T> response;
try {
response = parseResponse(rawResponse);//解析响应内容
} catch (Throwable e) {
callFailure(e);
return;
}
callSuccess(response);
}
}

从源码不难看出,首先通过createRawCall()来创建OkHttp中的Call对象,然后通过Call进行异步/同步调用,获取结果后通过parseResponse解析OkHttp中的Response,然后进行相应回调。 
createRawCall()用于创建OkHttp中的Call对象,源码如下:

private okhttp3.Call createRawCall() throws IOException {
Request request = serviceMethod.toRequest(args);//通过 serviceMethod.toRequest进行转换成Request
okhttp3.Call call = serviceMethod.callFactory.newCall(request);//调用newCall返回Call对象,默认callFactory为OkHttpClient,OkHttpClient也实现了okhttp3.Call.Factory接口。
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}

4 最后

Retrofit非常巧妙的用注解来描述一个HTTP请求,将一个HTTP请求抽象成一个Java接口,然后用了Java动态代理的方式,动态的将这个接口的注解“翻译”成一个HTTP请求,最后再执行这个HTTP请求

Retrofit的功能非常多的依赖Java反射,代码中其实还有很多细节,比如异常的捕获、抛出和处理,大量的Factory设计模式(为什么要这么多使用Factory模式?)

Retrofit中接口设计的恰到好处,在你创建Retrofit对象时,让你有更多更灵活的方式去处理你的需求,比如使用不同的Converter、使用不同的CallAdapter,这也就提供了你使用RxJava来调用Retrofit的可能

Retrofit 2.0 使用和原理的更多相关文章

  1. Retrofit 2.0 超能实践(一),okHttp完美支持Https传输

    http: //blog.csdn.net/sk719887916/article/details/51597816 Tamic首发 前阵子看到圈子里Retrofit 2.0,RxJava(Andro ...

  2. Retrofit 2.0 超能实践,完美支持Https传输

    http://blog.csdn.NET/sk719887916/article/details/51597816 前阵子看到圈子里Retrofit 2.0,RxJava(Android), OkHt ...

  3. Retrofit 2.0基于OKHttp更高效更快的网络框架 以及自定义转换器

    时间关系,本文就 Retrofit 2.0的简单使用 做讲解  至于原理以后有空再去分析 项目全面.简单.易懂  地址: 关于Retrofit 2.0的简单使用如下:  https://gitee.c ...

  4. Android Retrofit 2.0使用

    实例带你了解Retrofit 2.0的使用,分享目前开发Retrofit遇到的坑和心得. 添加依赖 app/build.gradle 1 compile 'com.squareup.retrofit2 ...

  5. Android Retrofit 2.0 使用-补充篇

    推荐阅读,猛戳: 1.Android MVP 实例 2.Android Retrofit 2.0使用 3.RxJava 4.RxBus 5.Android MVP+Retrofit+RxJava实践小 ...

  6. Retrofit 2.0使用

    最近在想能不能把之前项目里的网络请求改下 想通过Retrofit重构下,因为Retrofit完美兼容Rxjava后面会用到Rxjava 所以 开个坑写点 由于网上Retrofit 2.0的架构介绍很详 ...

  7. Retrofit 2.0 超能实践(四),完成大文件断点下载

    作者:码小白 文/CSDN 博客 本文出自:http://blog.csdn.net/sk719887916/article/details/51988507 码小白 通过前几篇系统的介绍和综合运用, ...

  8. Retrofit 2.0 超能实践(三),轻松实现文件/多图片上传/Json字符串

    文:http://blog.csdn.net/sk719887916/article/details/51755427 Tamic 简书&csdn同步 通过前两篇姿势的入门 Retrofit ...

  9. android -------- Retrofit + RxJava2.0 + Kotlin + MVP 开发的 WanAndroid 项目

    简介 wanandroid项目基于 Retrofit + RxJava2.0 + Kotlin + MVP 用到的依赖 implementation 'io.reactivex.rxjava2:rxj ...

随机推荐

  1. 01_python_初始python

    一.初始python python是一门解释型语言,弱类型语言 / python解释器最为常用的是cpython(官方) 弱类型语言:   a = 1 a = 'alex'   #说明变量a既可以是整 ...

  2. python中的三次握手以及四次挥手

    三次握手1.客户端对服务端说:我的序号是x,我要向你请求连接:(第一次握手,发送SYN包,然后进入SYN-SEND状态)2.服务端听到之后对客户端说:我的序号是y,期待你下一句序号是x+1的话(意思就 ...

  3. mybatis四大接口之 Executor

    [参考文章]:Mybatis-Executor解析 1. Executor的继承结构 2. Executor(顶层接口) 定义了执行器的一些基本操作: public interface Executo ...

  4. SpringMVC初探-HelloWorld

    MVC的概念 MVC是一种设计模式,即Model--View-Controller,模型--视图--控制器 Model(模型)表示应用程序核心(比如数据库记录列表). View(视图)显示数据(数据库 ...

  5. 课程二(Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimization),第三周(Hyperparameter tuning, Batch Normalization and Programming Frameworks) —— 2.Programming assignments

    Tensorflow Welcome to the Tensorflow Tutorial! In this notebook you will learn all the basics of Ten ...

  6. 课程二(Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimization),第一周(Practical aspects of Deep Learning) —— 4.Programming assignments:Gradient Checking

    Gradient Checking Welcome to this week's third programming assignment! You will be implementing grad ...

  7. 关于Spring Security中无Session和无状态stateless

    Spring Security是J2EE领域使用最广泛的权限框架,支持HTTP BASIC, DIGEST, X509, LDAP, FORM-AUTHENTICATION, OPENID, CAS, ...

  8. ecshop 安装出错gd_version

    678: static function gd_version()

  9. Yum安装Zabbix4.2.0

    目录 1. 下载所需的存储库 2. 安装zabbix 3. 安装mysql 4. 配置数据库 5. 基本配置 6. zabbix配置文件 7. 进入web安装zabbix 1. 下载所需的存储库 # ...

  10. Chapter 3 Phenomenon——20

    "All I know is that you weren't anywhere near me — 所有我知道的就是你当时不在我旁边的任何地方—— Tyler didn't see you ...