从零开始实现一个 mini-Retrofit 框架

前言

本篇文章将采用循序渐进的编码方式,从零开始实现一个Retorift框架,在实现过程中不断提出问题并分析实现,最终开发出一个mini版的Retrofit框架

img

演示一个使用OkHttp的项目Demo

为了更好的演示框架的实现过程,这里我先创建了一个简单的Demo项目

这个Demo项目中主要包含3个部分

  1. Json数据对应JavaEntity类
  2. 项目中包装网络请求回调的Callback
  3. 一个包含项目所有网络接口请求的管理类RestService

JavaBean

@Data
@ToString
public class BaseResponse<T> {private boolean error;private T results;
}
package com.knight.sample.entity;import java.util.List;
import java.util.Map;public class XianduResponse extends BaseResponse<List<GankEntity>> {
}

NetCallback

package com.knight.sample;import java.io.IOException;/*** 项目封装的统一网络请求的回调* @param <T>*/
public interface NetCallback<T> {void onFailure(Exception e);void onSuccess(T data);
}

NetWorkService

package com.knight.sample;import android.util.Log;import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;import java.io.IOException;import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;public class RestService {private static OkHttpClient okHttpClient;public static void init() {okHttpClient = new OkHttpClient.Builder().build();}public static<T>  void todayGank(Class<T> responseClazz,NetCallback<T> callback) {Request request = new Request.Builder().url("http://gank.io/api/today").get().build();okHttpClient.newCall(request).enqueue(new WrapperOkHttpCallback<>(responseClazz,callback));}public static<T>  void xianduGank(int count, int page,Class<T> responseClazz,NetCallback<T> callback) {Request request = new Request.Builder().url("http://gank.io/api/xiandu/data/id/appinn/count/" + count + "/page/" + page).get().build();okHttpClient.newCall(request).enqueue(new WrapperOkHttpCallback<>(responseClazz,callback));}static class WrapperOkHttpCallback<T> implements Callback {private static Gson gson = new Gson();private Class<T> clazz;private NetCallback<T> callback;public WrapperOkHttpCallback(Class<T> responseClazz, NetCallback<T> netCallback) {this.clazz = responseClazz;this.callback = netCallback;}@Overridepublic void onFailure(Call call, IOException e) {Log.e("WrapperOkHttpCallback", "onFailure");e.printStackTrace();callback.onFailure(e);}@Overridepublic void onResponse(Call call, Response response) throws IOException {JsonReader jsonReader = gson.newJsonReader(response.body().charStream());T entity = gson.getAdapter(clazz).read(jsonReader);Log.d("response", entity.toString());callback.onSuccess(entity);}}}

在NetworkService类中我们目前定义了2个Http 请求 todayGankxianduGank ,目前两个请求方式都是 Get 其中xianduGank 需要传入 countpage参数分别表示每页数据的数据以及请求的页码,除此之外这两个网络请求都需要传入 一个Class对象表示响应的Json数据对应的Model,以便在内部使用Gson来解析,以及网络请求的异步回调 NetCallback

我们不直接使用OkHttp提供的Callback 而是在内部简单的做了封装转换成项目自己的NetCallback,因为对项目的开发人员来说,更希望的是能够直接在Callback的success回调中直接得到响应的Json数据对应的JavaBean.

本次提交详细代码:https://github.com/Knight-ZXW/MiniRetrofit/commit/8c5443b752bd85706b4290c0b54b35a13e58c4e2

思考项目现状

上文模拟的代码只是一个简单的例子,可能会有更好的封装方式,但这并不是我们这篇文章想要讨论的重点。我们回到示例中RestService类中的代码部分,看下目前网络请求的写法

因为我们项目中已经有了OKHttp这个网络库了,有关Http具体的连接及通信的脏话累活都可以交给他来处理,对于项目开发者,事实上我们只需要配置以下Http请求部分

  • 请求的url 地址
  • 请求的方式 (GET、POST、PUT…)
  • 请求内容

假设我们已经具备了 Java注解 以及 动态代理的相关知识,知道以下信息

  • 注解可以添加在方法上
  • Retention为RUNTIME的注解可以在虚拟机运行时也获取到注解上的信息
  • Java的动态代理可以运行时生成原接口类型的代理实现类并hook方法的调用

每一个网络接口调用请求的url地址和请求方式都是唯一的 ,那么对于一个简单的网络请求 我们能不能使用 注解 + 动态代理来简化这一过程,改为声明式的编程方式来实现网络调用,比如就像这样

/*** Created by zhuoxiuwu* on 2019/4/25* email nimdanoob@gmail.com*/
public interface NetRestService {@GET("http://gank.io/api/today")public Call todayGank();
}

我们在一个抽象接口类中添加了一个方法,在方法上添加了注解**@GET** 表示这是一个Http GET请求的调用,注解中GET带的默认参数表示GET请求的地址。声明这个方法后,我们再通过Java动态代理技术在运行时解析这个方法上的注解的信息,内部通过调用OKHttp的相关方法生成一个 Call对象

有了大概思路了,我们接下来先简单的实现这样一个小例子来验证我们的想法是否可行

编码实现


3.1 简单实现一个支持GET、POST请求的Retrofit

新建一个注解类@GET

package retrofit2.http;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Created by zhuoxiuwu* on 2019/4/25* email nimdanoob@gmail.com*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GET {//注解中 方法名写成value 这样的话,在使用注解传入参数时就不用带key了,它会作为一个默认的调用String value();
}

新建一个处理Http接口类的动态代理的类Retrofit,因为我们实际网络请求的调用是依赖OKHttp,所以我们要求构造函数传入OkHttp对象 目前Retrofit 类只有一个方法publicT createService(final Classservice) 它接收一个抽象类,并生成该抽象类的代理实现。

package retrofit2;import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;import okhttp3.OkHttpClient;
import okhttp3.Request;
import retrofit2.http.GET;public class Retrofit {private OkHttpClient mOkHttpClient;public Retrofit(OkHttpClient mOkHttpClient) {this.mOkHttpClient = mOkHttpClient;}@SuppressWarnings("unchecked")public <T> T createService(final Class<T> service) {return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method,Object[] args) throws Throwable {//获取方法所有的注解final Annotation[] annotations = method.getAnnotations();for (int i = 0; i < annotations.length; i++) {if (annotations[i] instanceof GET) { //如果注解是GET类型final GET annotation = (GET) annotations[i];final String url = annotation.value();final Request request = new Request.Builder().url(url).get().build();return mOkHttpClient.newCall(request);}}return null;}});}
}

目前我们主要的目标是为了验证这个方案的可行性,因此createService方法内部的逻辑很简单

1.获取方法上的所有注解

  //获取方法所有的注解final Annotation[] annotations = method.getAnnotations();

2.判断如果存在@GET注解则获取注解内的值作为请求的地址

if (annotations[i] instanceof GET) { //如果注解是GET类型final GET annotation = (GET) annotations[i];final String url = annotation.value();

3.根据url构造GET请求的Request对象,并作为参数调用OkHttpClient的newCall方法生成Call对象作为该方法调用的返回值

 final Request request = new Request.Builder().url(url).get().build();return mOkHttpClient.newCall(request);

以上完成了一个对@GET注解申明的Http请求的动态代理封装,下面我们在自己的项目中验证一下

3.2 在项目中验证

1.创建一个接口类,并添加一个方法,方法的返回类型为Call,方法是添加了@GET注解

package com.knight.sample;import okhttp3.Call;
import retrofit2.http.GET;/*** Created by zhuoxiuwu* on 2019/4/25* email nimdanoob@gmail.com*/
public interface NetRestService {@GET("http://gank.io/api/today")public Call todayGank();
}

2.在项目中添加测试方法并调用

private void getToDayGankByRetrofit() {final Retrofit retrofit = new Retrofit(new OkHttpClient());retrofit.createService(NetRestService.class).todayGank().enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {JsonReader jsonReader = gson.newJsonReader(response.body().charStream());TodayGankResponse todayGankResponse = gson.getAdapter(TodayGankResponse.class).read(jsonReader);showHttpResult(todayGankResponse.toString());Log.d("RetrofitTest","调用成功,结果为"+todayGankResponse.toString());}});}

运行之后,方法调用成功并得到了响应结果

 D/RetrofitTest: 调用成功,结果为BaseResponse(error=false, results={Android=[GankEntity(url=https://github.com/iqiyi/Neptune, desc=适用于Android的灵活,强大且轻量级的插件框架...

通过简单的一个实现,我们成功验证了使用注解加动态代理的方式实现一个声明式的网络请求框架是可行的,那么后续我们需要继续完善这个项目,提供对更多请求方式 以及参数的支持

对于其他请求方式的支持,我们可以添加更多的表示请求方式的注解,当用户设置了不同的注解,在内部我们使用OKHttp调用相应的方法。Http的请求方式大概如下

  • @DELETE
  • @GET
  • @HEAD
  • @PATCH
  • @POST
  • @PUT
  • @OPTIONS

3.3 继续实现POST注解

为了加深理解,我们继续简单的实现一个POST请求,并支持传入一个参数对象,作为POST请求的JSON数据

首先我们添加一个POST注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface POST {String value();
}
package retrofit2;import com.google.gson.Gson;import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import retrofit2.http.GET;
import retrofit2.http.POST;public class Retrofit {private OkHttpClient mOkHttpClient;public Retrofit(OkHttpClient mOkHttpClient) {this.mOkHttpClient = mOkHttpClient;}@SuppressWarnings("unchecked")public <T> T createService(final Class<T> service) {return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method,Object[] args) throws Throwable {//获取方法所有的注解final Annotation[] annotations = method.getAnnotations();for (int i = 0; i < annotations.length; i++) {if (annotations[i] instanceof GET) { //如果注解是GET类型final GET annotation = (GET) annotations[i];return parseGet(annotation.value(), method, args);} else if (annotations[i] instanceof POST) {final POST annotation = (POST) annotations[i];return parsePost(annotation.value(), method, args);}}return null;}});}private Call parseGet(String url, Method method, Object args[]) {final Request request = new Request.Builder().url(url).get().build();return mOkHttpClient.newCall(request);}private Gson gson = new Gson();private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");private Call parsePost(String url, Method method, Object args[]) {final Type[] genericParameterTypes = method.getGenericParameterTypes();if (genericParameterTypes.length > 0) {final Class<?> clazz = Utils.getRawType(genericParameterTypes[0]);final String jsonBody = gson.toJson(args[0], clazz);final Request request = new Request.Builder().url(url).post(RequestBody.create(MEDIA_TYPE, jsonBody)).build();return mOkHttpClient.newCall(request);}return null;}}

在 paresePost方法中我们首先通过Method的getGenericParameterTypes方法获取所有参数的Type类型,并且通过Type类获得参数的原始Class类型,之后就可以使用Gson转换成对应的Json对象了。

3.4 实现ConverterFactory 解耦Json转换

在上面的例子中,我们直接在框架Retrofit中使用了Gson库做Json转换,但作为一个框架来说 我们不希望直接强耦合一个第三方Json转换库,这部分更希望交由开发者根据具体情况自由选择;因此我们可以对这部分做下抽象封装,提取成一个负责Json转换的接口 由应用层传入具体的实现.

package retrofit2;import java.lang.reflect.Type;import javax.annotation.Nullable;import okhttp3.RequestBody;/*** Created by zhuoxiuwu* on 2019/4/25* email nimdanoob@gmail.com*/
public interface Converter<F, T> {@NullableT convert(F value);abstract class Factory {public @NullableConverter<?, RequestBody> requestBodyConverter(Type type) {return null;}}
}

应用层需要传入一个ConverterFactory,该工厂类负责根据传入的Type类型,返回一个能够将该Type类型的对象转换成RequestBody的Converter

我们对Retrofit的构造函数以及paresePost方法做下修改,要求构造函数中传入一个ConverterFactory的实现,并在paresePost方法中使用这个ConverterFactory来做Java对象到ReqeustBody的转换

public class Retrofit {private OkHttpClient mOkHttpClient;private Converter.Factory mConverterFactory;public Retrofit(OkHttpClient mOkHttpClient, Converter.Factory mConverterFactory) {this.mOkHttpClient = mOkHttpClient;this.mConverterFactory = mConverterFactory;}//..省略部分代码private Call parsePost(String url, Method method, Object args[]) {final Type[] genericParameterTypes = method.getGenericParameterTypes();if (genericParameterTypes.length > 0) {//直接调用得到RequestBodyfinal RequestBody requestBody = requestBodyConverter(genericParameterTypes[0]).convert(args[0]);final Request request = new Request.Builder().url(url).post(requestBody).build();return mOkHttpClient.newCall(request);}return null;}public <T> Converter<T, RequestBody> requestBodyConverter(Type type) {return (Converter<T, RequestBody>) mConverterFactory.requestBodyConverter(type);}

在应用层,我们实现并传入一个Gson的ConvertFactory的实现

package com.knight.sample;import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonWriter;import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.charset.Charset;import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.Buffer;
import retrofit2.Converter;/*** Created by zhuoxiuwu* on 2019/4/25* email nimdanoob@gmail.com*/
public class GsonConverterFactory extends Converter.Factory {public static GsonConverterFactory create() {return create(new Gson());}public static GsonConverterFactory create(Gson gson) {if (gson == null) throw new NullPointerException("gson == null");return new GsonConverterFactory(gson);}private final Gson gson;private GsonConverterFactory(Gson gson) {this.gson = gson;}@Overridepublic Converter<?, RequestBody> requestBodyConverter(Type type) {//通过Type 转换成Gson的TypeAdapter//具体类型的json转换依赖于这个TypeAdapterTypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));return new GsonRequestBodyConverter<>(gson, adapter);}final static class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");private static final Charset UTF_8 = Charset.forName("UTF-8");private final Gson gson;private final TypeAdapter<T> adapter;GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {this.gson = gson;this.adapter = adapter;}@Overridepublic RequestBody convert(T value) {Buffer buffer = new Buffer();Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);JsonWriter jsonWriter = null;try {jsonWriter = gson.newJsonWriter(writer);adapter.write(jsonWriter, value);jsonWriter.close();return RequestBody.create(MEDIA_TYPE, buffer.readByteString());} catch (IOException e) {e.printStackTrace();return null;}}}}

3.5 实现CallAdapter 支持方法返回类型

继续回到Http请求的声明中,目前我们方法所支持的返回类型都是OKHttp的Call对象,而Call对象从使用上来说,目前还是有些繁琐,原生的Call对象返回的是ResponseBody还需要开发者自己处理并做转换。

public interface NetRestService {@GET("http://gank.io/api/today")public Call todayGank();
}

也许我们希望这个方法可以这样定义

public interface NetRestService {@GET("http://gank.io/api/today")public TodayGankResponse todayGank();
}

也许我们可以在框架内部通过判断方法的返回类型是不是Call对象,如果不是,就在框架内部直接同步调用网络请求得到响应的Json内容后直接转换成JavaBean对象作为方法的返回值,但是这个设想存在这样几个问题

  1. 要实现直接返回Http结果则方法调用是同步调用,如果在主线程做IO请求肯定是不合理的
  2. 如果内部IO异常了,或者JSON转换失败了方法返回的是什么呢?为null吗?

因此更合理的话,在应用我们希望的是返回一个包装的支持异步调用的类型

比如我们的项目自己新增了一个支持异步调用的NetCall抽象接口

/*** Created by zhuoxiuwu* on 2019/4/26* email nimdanoob@gmail.com*/
public interface NetCall<T> {public void execute(NetCallback<T> netCallback);
}

我们希望我们的方法可以这样申明

public interface NetRestService {@GET("http://gank.io/api/today")public NetCall<TodayGankResponse> todayGank();
}

这样的话在应用层我们调用的时候就可以像这样使用

        retrofit.createService(NetRestService.class).todayGank().execute(new NetCallback<TodayGankResponse>() {@Overridepublic void onFailure(Exception e) {}@Overridepublic void onSuccess(TodayGankResponse data) {Log.d("RetrofitTest","调用成功,结果为"+data.toString());showHttpResult(data.toString());}});

那么具体要怎么实现呢,这个功能相当于让Retrofit框架支持 对方法返回类型的自定义适配,和Converter接口一样的思路,我们在框架可以定义一个 CallAdapter接口,让应用层来具体实现并传入

package retrofit2;import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;import okhttp3.Call;/*** Created by zhuoxiuwu* on 2019/4/26* email nimdanoob@gmail.com*/
public interface CallAdapter<T> {T adapt(Call call);abstract class Factory {public abstract CallAdapter<?> get(Type returnType,Retrofit retrofit);/*** 这是一个框架提供给开发者的util方法* 用于获取类型的泛型上的类型* 比如 Call<Response> 则 第0个泛型是Response.class*/protected static Type getParameterUpperBound(int index, ParameterizedType type) {return Utils.getParameterUpperBound(index, type);}/*** 获取Type对应的Class* @param type* @return*/protected static Class<?> getRawType(Type type) {return Utils.getRawType(type);}}
}

在应用层我们可以实现一个NetCallAdapter,支持Call对象到 NetCall对象的转换

package com.knight.sample;import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;/*** Created by zhuoxiuwu* on 2019/4/26* email nimdanoob@gmail.com*/
package com.knight.sample;import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;/*** Created by zhuoxiuwu* on 2019/4/26* email nimdanoob@gmail.com*/
public class NetCallAdapterFactory extends CallAdapter.Factory {/*** returnType参数 和 retroift参数 由底层框架传递给开发者* @param returnType* @param retrofit* @return*/@Overridepublic CallAdapter<?> get(final Type returnType, final Retrofit retrofit) {//判断返回类型是否是 NetCallif (getRawType(returnType) != NetCall.class) {return null;}//要求开发者方法的返回类型必须写成 NetCall<T> 或者NetCall<? extends Foo> 的形式,泛型内的类型就是Json数据对应的Classif (!(returnType instanceof ParameterizedType)) {throw new IllegalStateException("NetCall return type must be parameterized as NetCall<Foo> or NetCall<? extends Foo>");}final Type innerType = getParameterUpperBound(0, (ParameterizedType) returnType);return new CallAdapter<NetCall>() {@Overridepublic NetCall adapt(final Call call) {return new NetCall() {@Overridepublic void execute(final NetCallback netCallback) {call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {netCallback.onFailure(e);}@Overridepublic void onResponse(Call call, Response response) throws IOException {//由retrofit 提供 ResponseBody 到 某个Type Class的转换final Object value = retrofit.responseBodyTConverter(innerType).convert(response.body());netCallback.onSuccess(value);}});}};}};}
}

框架的后续实现及优化

到目前为止我们已经实现了一个简单的Retrofit框架,也许代码不够精简,边界处理没有十分严谨,但已经初具雏形。我们可以继续思考现有项目的不足,添加更多的支持。

比如在网络请求方面目前只支持GET、POST,那么我们后续需要添加更多请求方式的支持。

以上提出的一些优化点,大家可以自己先思考实现并重新阅读写Retrofit源码来加深自己的理解。从整个思考流程及实现上来看Retrofit的实现并不复杂,但是从实现一个简单可用的网络封装库到实现一个拓展性强、职责分离的框架,中间的过程还是有很多细节的,如果你看完了这篇文章,可以再抽1个小时左右的时间重新看下Retorift框架的源码,相信从中还会有更多的收获。

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2023最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/78747.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mongodb-win32-x86_64-2008plus-3.4.24-signed.msi

Microsoft Windows [版本 6.1.7601] 版权所有 (c) 2009 Microsoft Corporation。保留所有权利。C:\Users\Administrator>cd C:\MongoDB\Server\3.4\binC:\MongoDB\Server\3.4\bin>C:\MongoDB\Server\3.4\bin>mongod --help Options:General options:-h [ --help ] …

【MySQL】增删查改基础

文章目录 一、创建操作1.1 单行插入1.2 多行插入1.3 插入否则替换更新1.4 替换replace 二、查询操作2.1 select查询2.2 where条件判断2.3 order by排序2.4 limit筛选分页结果 三、更新操作四、删除操作4.1 删除一列4.2 删除整张表数据 五、插入查询结果 CRUD : Create(创建), R…

CS 144 Lab Four 收尾 -- 网络交互全流程解析

CS 144 Lab Four 收尾 -- 网络交互全流程解析 引言Tun/Tap简介tcp_ipv4.cc文件配置信息初始化cs144实现的fd家族体系基于自定义fd体系进行数据读写的adapter适配器体系自定义socket体系自定义事件循环EventLoop模板类TCPSpongeSocket详解listen_and_accept方法_tcp_main方法_in…

【雕爷学编程】Arduino动手做(188)---0.66寸OLED液晶屏模块

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

Source Insight显示行号

1、默认不显示行号 (1)Source Insight默认是不显示行号的&#xff0c;如下图所示。 2、配置显示行号 2.1、方法1 (1)点击View→Line Numbers。 2.2、方法2 (1)空白处鼠标右键&#xff0c;在点击"Line Numbers"。 (2)配置显示行号后如下图所示。

使用hexo进行博客迁移

本文不会从0开始介绍如何通过hexo去搭建一个github page。因为最近折腾了下&#xff0c;发现这玩意儿确实写个博客很费劲&#xff0c;打算把他拖管到github当作我的知识库网站&#xff0c;我的主要文章还是通过mweb写完一键发布到博客园&#xff0c;然后csdn记录一些杂文和思考…

Liunx环境下git的详细使用(gitee版)

Liunx环境下git的详细使用&#xff08;gitee版&#xff09; 1.git是什么2.git操作2.1在gitee创建一个仓库2.2.gitignore2.3.git 3.git三板斧3.1add3.2 commit3.3push 4.git其他命令4.1查看当前仓库状态4.2查看提交日志4.3修改git里面文件名称4.4删除文件4.5修改远端仓库内容 1.…

低碳 Web 实践指南

现状和问题 2023年7月6日&#xff0c;世界迎来有记录以来最热的一天。气候变化是如今人类面临的最大健康威胁。据世界卫生组织预测2030年至2050年期间&#xff0c;气候变化预计每年将造成约25万人死亡。这是人们可以真切感受到的变化&#xff0c;而背后的主要推手是碳排放。 …

Java分布式微服务2——声明式Http客户端(Feign)与网关(Gateway)

文章目录 Http声明式客户端FeignFeign介绍与使用Feign自定义配置Feign性能优化Feign最佳实践方案 网关Gateway网关Gateway的作用与搭建路由断言工厂Route Predicate Factory路由过滤器GatewayFilter全局过滤器过滤器执行顺序网关的跨域处理 Http声明式客户端Feign Feign介绍与…

Python魔法解析:探索变量类型的丰富多彩世界!

在Python这个魔法般的编程语言中&#xff0c;变量是连接你与计算机世界的神奇桥梁。然而&#xff0c;这些变量并不是单一的&#xff0c;它们有着丰富多彩的类型。无论你是刚刚踏入编程的大门&#xff0c;还是想要深入了解Python的高级特性&#xff0c;本篇博客将带你探索变量的…

c++学习(特殊类设计)[30]

只能在堆上创建对象的类 如果你想要确保对象只能在堆上创建&#xff0c;可以通过将析构函数声明为私有&#xff0c;并提供一个静态成员函数来创建对象。这样&#xff0c;类的实例化只能通过调用静态成员函数来完成&#xff0c;而无法直接在栈上创建对象。 以下是一个示例&…

【PCIE】PCIE的驱动和pcie的端口驱动关系

pice驱动和pcie端口驱动区别 PCIe的端口服务驱动与PCIe驱动之间存在一定的关系&#xff0c;但它们是不同的概念。 PCIe驱动是用于管理和操作PCIe设备的驱动程序。它负责与硬件进行通信&#xff0c;并实现对PCIe设备的配置、数据传输以及其他相关操作。PCIe驱动通常涉及设备的…

【NLP概念源和流】 05-引进LSTM网络(第 5/20 部分)

一、说明 在上一篇博客中,我们讨论了原版RNN架构,也讨论了它的局限性。梯度消失是一个非常重要的缺点,它限制了RNN对较短序列的建模。香草 RNN 在相关输入事件和目标信号之间存在超过 5-10 个离散时间步长的时间滞时无法学习。这基本上限制了香草RNN在许多实际问题上的应用,…

NestJs Debug配置文件

&#xff08;事缓则圆,人缓则安,语迟则贵,虎行似病,鹰立似睡。清俞万春《荡寇志》&#xff09; {"version": "0.2.0","configurations": [{"type": "node","request": "launch","name": &quo…

基于LNMP架构搭建Discuz论坛

LNMP: L---->linux系统&#xff0c;操作系统。 N----->nginx网站服务&#xff08;前端),提供前端的静态页面服务。同时具有代理、转发的作用。&#xff08;转发就是转发后端请求&#xff0c;转发PHP&#xff09;&#xff0c;nginx没有处理动态资源的功能&#xff0c;他有…

IO模型-信号驱动IO

linux内核中存在一个信号SIGIO&#xff0c;这个信号就是用于实现信号驱动IO的。当应用程序中想要以信号驱动IO的模型读写硬件数据时&#xff0c;首先注册一个SIGIO信号的信号处理函数,当硬件数据就绪&#xff0c;硬件会发起一个中断&#xff0c;在硬件的中断处理函数中向当前进…

Plus 框架分页合理化问题

需要关闭 MyBatis Plus的分页合理化 RuoYi-Vue-Plus框架默认的Mybatis Plus分页拦截器配置是打开了分页合理化&#xff0c;这样会导致溢出的分页数据本来应该返回空数据&#xff0c;打开之后而会永远返回默认的前10条数据。 /*** mybatis-plus配置类(下方注释有插件介绍)** au…

CSS元素的显示模式

1、现在我想做成小米左侧边栏这样的效果&#xff0c;该怎么做呢&#xff1f; 2、小米商城触碰之后会显示出新的商品案例 3、一碰到之后会出现这个列表 4、这里涉及到了元素显示模式&#xff1a; 5、用人进行划分可以分为男人和女人&#xff0c;根据男人和女人的特性进行相应的…

【ChatGPT 指令大全】怎么利用ChatGPT写报告

目录 选定切入角度 报告开头 大纲生成 草稿撰写 研究报告 提出反对观点 报告总结 研究来源 总结 随着人工智能技术的快速发展&#xff0c;自然语言处理技术在各个领域的应用越来越广泛。其中&#xff0c;ChatGPT作为目前最先进的自然语言处理模型之一&#xff0c;其强…

读写分离实现sharding-jdbc

一、背景 二、使用sharding-JDBC实现 1、导入依赖 2、配置文件&#xff08;名字要对应&#xff09; ​​​​​​​ 查询策略&#xff08;轮询&#xff0c;指定哪个是主库哪个是从库&#xff09; 允许bean定义覆盖