Android模擬簡單的網絡請求框架Retrofit實現

文章目錄

    • 1.靜態代理
    • 2.動態代理
    • 3.實現簡單的Retrofit
      • 定義對應的請求注解參數
      • 通過動態代理模擬Retrofit的創建
      • 請求參數的處理
      • 定義請求接口
      • 測試請求

1.靜態代理

代理默認給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用。通俗來講,代理模式就是我們生活中常見的中介。比如你按照小卡片上的電話打過去尋求服務,一般不是由本人,可能是一個成年雄性接聽電話,然后真正做事情的可能就是另一個小姐姐了。

目的:

  1. 通過引入代理對象的方式來間接訪問目標對象,防止直接訪問目標對象給系統帶來的不必要復雜性;
  2. 通過代理對象來訪問控制;

在這里插入圖片描述
代理模式一般會有三個角色:
抽象角色:指代理角色和真實角色對外提供的公共方法,一般為一個接口
真實角色:需要實現抽象角色接口,定義了真實角色所實現的業務邏輯,以便供代理角色調用。也就是真正的業務邏輯。
代理角色:需要實現抽象角色接口,是真實角色的代理,通過真實角色的業務邏輯方法來實現抽象方法,并可以附加自己的操作。將統一的流程控制都放到代理角色中處理。

比如:抽象角色
創建一個接口

public interface Image {void display();
}

代理角色:

public class ProxyImage implements Image{private RealImage realImage;private String fileName;public ProxyImage(String fileName){this.fileName = fileName;}@Overridepublic void display() {if(realImage == null){realImage = new RealImage(fileName);}realImage.display();}
}

真實角色:

public class RealImage implements Image {private String fileName;public RealImage(String fileName){this.fileName = fileName;loadFromDisk(fileName);}@Overridepublic void display() {System.out.println("Displaying " + fileName);}private void loadFromDisk(String fileName){System.out.println("Loading " + fileName);}
}

當被請求的時候,則使用代理類獲取來獲取真實對象

public class ProxyPatternDemo {public static void main(String[] args) {Image image = new ProxyImage("test_10mb.jpg");// 圖像將從磁盤加載image.display(); System.out.println("");// 圖像不需要從磁盤加載image.display();  }
}

打印:

Loading test_10mb.jpg
Displaying test_10mb.jpgDisplaying test_10mb.jpg

優點:

  • 可以在不修改目標對象的前提下,增加功能
  • 保護目標對象,客戶端不直接訪問真實對象

缺點:
一個代理類只能代理一個接口/類,代碼冗余嚴重
比如你還要代理一個其他類,就要再寫一個對應的代理類,重復代碼

舉個例子來對比:

靜態代理:新增接口 ->必須改代碼,寫新代理類
動態代理:新增接口 ->完全不用改代碼,直接支持

假設有一個服務平臺,現在有兩類服務:
抽象角色類:

// 發送短信
public interface SmsService {void sendSms(String msg);
}// 發送郵件
public interface EmailService {void sendEmail(String to, String subject);
}

真實實現類:

public class SmsServiceImpl implements SmsService {@Overridepublic void sendSms(String msg) {System.out.println("發送短信: " + msg);}
}public class EmailServiceImpl implements EmailService {@Overridepublic void sendEmail(String to, String subject) {System.out.println("發送郵件到 " + to + ",主題: " + subject);}
}

靜態代理的痛苦,每新增一個接口就要寫一個代理類
起初只有SmsService,寫了一個代理

public class SmsServiceProxy implements SmsService {private final SmsService smsService;public SmsServiceProxy(SmsService smsService) {this.smsService = smsService;}@Overridepublic void sendSms(String msg) {System.out.println("【日志】準備發送短信...");smsService.sendSms(msg);System.out.println("【日志】短信發送完成!");}
}

現在要加EmailService,必須再寫一個代理類

public class EmailServiceProxy implements EmailService {private final EmailService emailService;public EmailServiceProxy(EmailService emailService) {this.emailService = emailService;}@Overridepublic void sendEmail(String to, String subject) {System.out.println("【日志】準備發送郵件...");emailService.sendEmail(to, subject);System.out.println("【日志】郵件發送完成!");}
}

你會發現:兩個代理類的邏輯一模一樣,只是接口不同,但你必須要寫兩個類,否則無法代理。
如果再加WeChatService、PushService…則需要寫WeChatServiceProxy、PushServiceProxy…

當然,也可以代理類和實現類同時實現兩個接口

// 手動實現兩個接口
public class NotificationProxy implements SmsService, EmailService {private final NotificationServiceImpl target;public NotificationProxy(NotificationServiceImpl target) {this.target = target;}@Overridepublic void sendSms(String msg) {System.out.println("【靜態代理】準備發短信...");target.sendSms(msg);System.out.println("【靜態代理】短信發送完成");}@Overridepublic void sendEmail(String to, String subject) {System.out.println("【靜態代理】準備發郵件...");target.sendEmail(to, subject);System.out.println("【靜態代理】郵件發送完成");}
}
public class NotificationServiceImpl implements SmsService, EmailService {@Overridepublic void sendSms(String msg) {System.out.println("發送短信: " + msg);}@Overridepublic void sendEmail(String to, String subject) {System.out.println("發送郵件到: " + to + ",主題: " + subject);}
}
NotificationProxy proxy = new NotificationProxy(new NotificationServiceImpl());SmsService smsProxy = proxy;
smsProxy.sendSms("你好");EmailService emailProxy = proxy;
emailProxy.sendEmail("user@abc.com", "測試");

但這樣的話,代碼重復,維護困難
每個方法都要手動寫一遍代碼邏輯
如果新增WeChatService接口,你必須

  • 修改NotificationProxy類
  • 添加implements WeChatService
  • 實現sendWeChat()方法

2.動態代理

在運行時再創建代理類和其實例,因此顯然效率更低。要完成這個場景,需要在運行期間動態創建一個Class。JDK提供了Proxy來完成這件事情。基本使用如下:
比如動態代理可以同時代理兩個接口

NotificationServiceImpl target = new NotificationServiceImpl();// 創建代理,讓它同時實現兩個接口
Object proxy = Proxy.newProxyInstance(Test.class.getClassLoader(),new Class[]{SmsService.class, EmailService.class},  // 同時代理兩個接口new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("【日志】調用方法: " + method.getName() + " 開始...");Object result = method.invoke(target, args);System.out.println("【日志】調用方法: " + method.getName() + " 結束.");return result;}}
);
// 當作 SmsService 使用
SmsService smsProxy = (SmsService) proxy;
smsProxy.sendSms("你好啊");// 當作 EmailService 使用
EmailService emailProxy = (EmailService) proxy;
emailProxy.sendEmail("user@abc.com", "訂單確認");

輸出結果

【日志】調用方法: sendSms 開始...
發送短信: 你好啊
【日志】調用方法: sendSms 結束.【日志】調用方法: sendEmail 開始...
發送郵件到: user@abc.com,主題: 訂單確認
【日志】調用方法: sendEmail 結束.

動態代理也可以需要一個通用的處理器,就能代理任意接口

public class LogInvocationHandler implements InvocationHandler {private final Object target;//真實對象public LogInvocationHandler(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("日志調用方法:"+method.getName()+"開始...");Object result = method.invoke(target,args);//調用真實方法System.out.println("日志調用方法:"+method.getName()+"結束...");return result;}
}
public class Test {public static void main(String[] args) {//代理SmsServiceSmsService sms = new SmsServiceImpl();SmsService smProxy = (SmsService) Proxy.newProxyInstance(Test.class.getClassLoader(),new Class[]{SmsService.class},new LogInvocationHandler(sms));smProxy.sendSms("你好啊");//代理EmailServiceEmailService email = new EmailServiceImpl();EmailService emailProxy = (EmailService)Proxy.newProxyInstance(Test.class.getClassLoader(),new Class[]{EmailService.class},new LogInvocationHandler(email));emailProxy.sendEmail("user@abc.com","訂單確認");}
}

輸出結果

日志調用方法:sendSms開始...
發送短信:你好啊
日志調用方法:sendSms結束...
日志調用方法:sendEmail開始...
發送郵件到 user@abc.com,主題:訂單確認
日志調用方法:sendEmail結束...

注意要避免的問題

public class LogInvocationHandler implements InvocationHandler {private final Object target;//真實對象public LogInvocationHandler(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(proxy);System.out.println("日志調用方法:"+method.getName()+"開始...");Object result = method.invoke(target,args);//調用真實方法System.out.println("日志調用方法:"+method.getName()+"結束...");return result;}
}

如果你在動態代理的InvocationHandler 里的invoke方法打印一下proxy,你會發現報了棧溢出的錯
在這里插入圖片描述
因為你打印proxy其實默認會走proxy的toString()方法也會回到到invoke方法造成棧溢出
我們也可以看一下Retrofit的代碼,可以看到當使用到Object的方法的時候,傳遞的是this,而不是proxy代理對象。如果你寫 method.invoke(proxy, args),可能會再次觸發 invoke(),導致無限遞歸!所以 Retrofit 選擇調用 this,避免循環。
在這里插入圖片描述
在生成Class文件的時候,有兩種方式一種是.java源文件通過javac編譯生成的.class文件,再通過類加載生成Class對象,這種方式生成的class文件來源是在硬盤。而動態代理生成的class文件來源在內存生成的。
動態代理生成的源代碼

 @CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();// Android-removed: SecurityManager calls/*final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}*//** Look up or generate the designated proxy class.*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {// Android-removed: SecurityManager / permission checks./*if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}*/final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {// BEGIN Android-removed: Excluded AccessController.doPrivileged call./*AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});*/cons.setAccessible(true);// END Android-removed: Excluded AccessController.doPrivileged call.}return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}}
Class<?> cl = getProxyClass0(loader, interfaces); ↓
JVM 調用 ProxyGenerator 生成字節碼↓
ProxyGenerator.generateProxyClass(...) 返回 byte[],生成的Class數據的byte數組↓
JVM defineClass(byte[]) 加載這個類,將這個class數據轉成一個Class對象

驗證

        String name = SmsService.class.getName()+"$proxy0";//兩個參數,生成的類名,代理的接口byte[] bytes = ProxyGenerator.generateProxyClass(name,new Class[]{SmsService.class});FileOutputStream fos = new FileOutputStream("src/"+name+".class");fos.write(bytes);fos.close();

在這里插入圖片描述

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//package agent;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class SmsService$proxy0 extends Proxy implements SmsService {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public SmsService$proxy0(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final void sendSms(String var1) throws  {try {super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("agent.SmsService").getMethod("sendSms", Class.forName("java.lang.String"));m2 = Class.forName("java.lang.Object").getMethod("toString");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

所以我們使用動態代理,Proxy.newProxyInstance代理SmsService接口生成的.class文件就是上面的樣子

SmsService sms = new SmsServiceImpl();SmsService smProxy = (SmsService) Proxy.newProxyInstance(Test.class.getClassLoader(),new Class[]{SmsService.class},new LogInvocationHandler(sms));smProxy.sendSms("你好啊");smProxy.toString();

這里可以看到SmsService$proxy0類里的構造方法可以看到super(var1);會將new LogInvocationHandler(sms)傳遞進去。
super(var1)進入Proxy.java類的構造方法

    protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}

可以看到將當前的this.h=h,賦值給當前類的InvocationHandler
而當調用smProxy.sendSms(“你好啊”);的時候就會調用SmsService$proxy0類里sendSms方法

    public final void sendSms(String var1) throws  {try {super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}

這個時候調用super.h.invoke(this, m3, new Object[]{var1}),就是將對應的信息回調給LogInvocationHandler里的invoke方法。
this就是當前生成的代理類對象。
m3可以看到Class.forName(“agent.SmsService”).getMethod(“sendSms”, Class.forName(“java.lang.String”))
通過反射的方式生成對應的method對象。

3.實現簡單的Retrofit

定義對應的請求注解參數

@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Field {String value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GET {String value() default "";
}
@Target(ElementType.METHOD)//作用在方法上
@Retention(RetentionPolicy.RUNTIME) //保留期在運行期間
public @interface POST {String value() default "";
}
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Query {String value();
}

通過動態代理模擬Retrofit的創建

/*** 通過動態代理模擬Retrofit的創建*/
public class MyRetrofit {final Map<Method,ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>();final Call.Factory callFactory;final HttpUrl baseUrl;MyRetrofit(Call.Factory callFactory,HttpUrl baseUrl){this.callFactory = callFactory;this.baseUrl = baseUrl;}public <T> T create(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 {//解析這個method上所有注解ServiceMethod serviceMethod = loadServiceMethod(method);//args就是傳遞參數的值return serviceMethod.invoke(args);}});}private ServiceMethod loadServiceMethod(Method method) {//先不上鎖,避免synchronized的性能損失ServiceMethod result = serviceMethodCache.get(method);if(result!=null) return result;//多線程下,避免重復解析synchronized (serviceMethodCache){result = serviceMethodCache.get(method);if(result==null){result = new ServiceMethod.Builder(this,method).build();serviceMethodCache.put(method,result);}}return result;}/*** 構建者模式,將一個復雜對象的構建和它的表示分離,可以使用者不必知道內部組成的細節*/public static final class Builder{private HttpUrl baseUrl;private Call.Factory callFactory;public Builder callFactory(Call.Factory factory){this.callFactory = factory;return this;}public Builder baseUrl(String baseUrl){this.baseUrl = HttpUrl.get(baseUrl);return this;}public MyRetrofit build() throws IllegalAccessException {if(baseUrl == null){throw new IllegalAccessException("Base URL required");}Call.Factory callFactory = this.callFactory;if(callFactory == null){callFactory = new OkHttpClient();}return new MyRetrofit(callFactory,baseUrl);}}}

請求參數的處理

public class ServiceMethod {private final Call.Factory callFactory;private final String relativeUrl;private final boolean hasBody;private final ParameterHandler[] parameterHandler;HttpUrl baseUrl;String httpMethod;private FormBody.Builder formBuild;HttpUrl.Builder urlBuilder;public ServiceMethod(Builder builder) {baseUrl = builder.myRetrofit.baseUrl;callFactory = builder.myRetrofit.callFactory;httpMethod = builder.httpMethod;relativeUrl = builder.relativeUrl;hasBody = builder.hasBody;parameterHandler = builder.parameterHandler;//如果是由請求體,創建一個okhttp的請求體對象if(hasBody){formBuild = new FormBody.Builder();}}public Object invoke(Object[] args) {/*** 處理請求的地址與參數* parameterHandler存的key的順序和args參數的值順序一一對應*/for (int i = 0; i < parameterHandler.length; i++) {ParameterHandler handlers = parameterHandler[i];//handler內本來就記錄了key,現在給到對應的valuehandlers.apply(this,args[i].toString());}//獲取最終請求地址HttpUrl url;if(urlBuilder == null){urlBuilder = baseUrl.newBuilder(relativeUrl);}url = urlBuilder.build();//請求體FormBody formBody = null;if(formBuild!=null){formBody = formBuild.build();}Request request = new Request.Builder().url(url).method(httpMethod,formBody).build();return callFactory.newCall(request);}//get請求,把k-v拼到url里面public void addQueryParameter(String key, String value) {if(urlBuilder == null){urlBuilder = baseUrl.newBuilder(relativeUrl);//將baseUrl和relativeUrl拼到一起}//在url后面加k-vurlBuilder.addQueryParameter(key,value);}//post請求,把k-v放到請求體中public void addFiledParameter(String key, String value) {formBuild.add(key,value);}public static class Builder{private final MyRetrofit myRetrofit;private final Annotation[] methodAnnotations;private final Annotation[][] parameterAnnoations;private String httpMethod;private String relativeUrl;private boolean hasBody;private ParameterHandler[] parameterHandler;public Builder(MyRetrofit myRetrofit, Method method){this.myRetrofit = myRetrofit;//獲取方法上的所有的注解methodAnnotations = method.getAnnotations();//獲得方法參數的所有的注解(一個參數可以有多個注解,一個方法又會有多個參數)parameterAnnoations = method.getParameterAnnotations();}public ServiceMethod build(){/*** 解析方法上的注解,只處理POST和GET*/for (Annotation methodAnnotation : methodAnnotations) {if(methodAnnotation instanceof POST){//記錄當前請求方式this.httpMethod = "POST";//記錄請求url的paththis.relativeUrl = ((POST) methodAnnotation).value();//是否有請求體this.hasBody = true;}else if(methodAnnotation instanceof GET){this.httpMethod = "GET";this.relativeUrl = ((GET) methodAnnotation).value();this.hasBody = false;}}/*** 解析方法參數的注解*/int length = parameterAnnoations.length;parameterHandler = new ParameterHandler[length];for(int i =0;i<length;i++){//一個參數上的所有注解Annotation[] annotations = parameterAnnoations[i];//處理參數上的每一個注解for (Annotation annotation : annotations) {//可以加一個判斷,如果httpMethod是get請求,現在又解析到Filed注解,可以提示使用者使用Query注解if(annotation instanceof Field){String value = ((Field) annotation).value();parameterHandler[i] = new ParameterHandler.FiledParameterHandler(value);//等于在一個新的類中記錄key}else if(annotation instanceof Query){String value = ((Query) annotation).value();parameterHandler[i] = new ParameterHandler.QueryParameterHandler(value);}}}return new ServiceMethod(this);}}}
public abstract class ParameterHandler {abstract void apply(ServiceMethod serviceMethod,String value);static class QueryParameterHandler extends ParameterHandler{String key;public QueryParameterHandler(String key){this.key = key;}@Overridevoid apply(ServiceMethod serviceMethod, String value) {serviceMethod.addQueryParameter(key,value);}}static class FiledParameterHandler extends ParameterHandler{String key;public FiledParameterHandler(String key){this.key = key;}@Overridevoid apply(ServiceMethod serviceMethod, String value) {serviceMethod.addFiledParameter(key,value);}}}

定義請求接口

//https://www.wanandroid.com/article/list/0/json?cid=60
public interface MyTestApi {@GET("/article/list/0/json")Call getTestApi(@Query("cid") String cid);/*@GET("/project/list/1/json")Call getWeather(@Query("cid") String cid);
*/@POST("/user/login")Call postTestApi(@Field("username") String username, @Field("password") String password);}

測試請求

class MainActivity3 : AppCompatActivity() {@SuppressLint("MissingInflatedId", "SetTextI18n")override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main3)val myRetrofit = MyRetrofit.Builder().baseUrl("https://www.wanandroid.com").build()val weatherApi = myRetrofit.create(MyTestApi::class.java)val call = weatherApi.getTestApi("60")call.enqueue(object :  Callback {override fun onFailure(call: Call, e: IOException) {Log.d("cyr", "onFailure: $e")}override fun onResponse(call: Call, response: Response) {Log.d("cyr","onResponse get:"+response.body())}})val postCall = weatherApi.postTestApi("xxxx","xxxx")postCall.enqueue(object : Callback{override fun onFailure(call: Call, e: IOException) {}override fun onResponse(call: Call, response: Response) {val body = response.body()try {val string = body!!.string()Log.i("cyr", "onResponse post: $string")} catch (e: IOException) {e.printStackTrace()} finally {body!!.close()}}})}}

在這里插入圖片描述
可以看到輸出了請求成功結果日志。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/98144.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/98144.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/98144.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Matter安全實現

Matter分析與安全驗證 上一篇文章簡單的介紹了Matter的架構、實現、以及部分安全驗證過程&#xff1b;這里繼續補充一下Matter的其他安全驗證流程&#xff0c;以更好的實現Matter安全。 Matter提供的安全實現流程大概總結起來是這個流程 硬件信任根→安全啟動→動態證書→加密…

從基礎到實踐:Web核心概念與Nginx入門全解析

從基礎到實踐&#xff1a;Web核心概念與Nginx入門全解析 文章目錄從基礎到實踐&#xff1a;Web核心概念與Nginx入門全解析一、Web是什么&#xff1f;從基本概念到核心架構1.1 Web的本質&#xff1a;一個超文本信息系統1.2 B/S架構&#xff1a;Web的“前端-后端”分工模式二、一…

【完整源碼+數據集+部署教程】加工操作安全手套與手部檢測系統源碼和數據集:改進yolo11-cls

背景意義 研究背景與意義 隨著工業自動化和智能制造的迅速發展&#xff0c;工人安全問題日益受到重視。特別是在涉及重型機械和危險操作的工作環境中&#xff0c;工人手部的安全保護顯得尤為重要。傳統的安全手套雖然在一定程度上能夠保護工人的手部&#xff0c;但在復雜的加工…

代碼隨想錄算法訓練營第一天 || (雙指針)27.移除元素 26.刪除有序數組中的重復項 283.移動零 977.有序數組的平方

代碼隨想錄算法訓練營第一天 || (雙指針)27.移除元素 26.刪除有序數組中的重復項 283.移動零 27.移除元素 暴力方法 同向雙指針雙指針 自己AC的解答 卡哥的講解 26.刪除有序數組中的重復項 同向雙指針 283.移動零 自己解答 靈神做法(同向雙指針+交換) 977.有序數組的平方 暴…

Java全棧開發工程師面試實錄:從基礎到實戰的深度探討

Java全棧開發工程師面試實錄&#xff1a;從基礎到實戰的深度探討 一、初識與自我介紹 面試官&#xff08;李工&#xff09;&#xff1a; 你好&#xff0c;歡迎來到我們公司。我是負責技術面試的李工&#xff0c;今天我們將進行一場關于Java全棧開發的深入交流。你可以先簡單介紹…

Kafka:Java開發的消息神器,你真的懂了嗎?

Kafka&#xff1a;Java開發的消息神器&#xff0c;你真的懂了嗎&#xff1f; 一、Kafka 是什么鬼&#xff1f; 想象一下&#xff0c;你在網上瘋狂剁手后&#xff0c;滿心期待著快遞包裹的到來。這時候&#xff0c;快遞站就像是 Kafka&#xff0c;而你的包裹就是消息。快遞站接…

深度學習之第八課遷移學習(殘差網絡ResNet)

目錄 簡介 一、遷移學習 1.什么是遷移學習 2. 遷移學習的步驟 二、殘差網絡ResNet 1.了解ResNet 2.ResNet網絡---殘差結構 三、代碼分析 1. 導入必要的庫 2. 模型準備&#xff08;遷移學習&#xff09; 3. 數據預處理 4. 自定義數據集類 5. 數據加載器 6. 設備配置…

Pinia 兩種寫法全解析:Options Store vs Setup Store(含實踐與場景對比)

目標&#xff1a;把 Pinia 的兩種寫法講透&#xff0c;寫明“怎么寫、怎么用、怎么選、各自優缺點與典型場景”。全文配完整代碼與注意事項&#xff0c;可直接當團隊規范參考。一、背景與準備 適用版本&#xff1a;Vue 3 Pinia 2.x安裝與初始化&#xff1a; # 安裝 npm i pini…

setup函數相關【3】

目錄1.setup函數&#xff1a;1.概述&#xff1a;2.案例分析&#xff1a;2.setup函數的優化&#xff1a;&#xff08;setup語法糖&#xff09;優化1&#xff1a;優化2&#xff1a;安裝插件&#xff1a;安裝指令&#xff1a;只對當前項目安裝配置vite.config.ts&#xff1a;代碼編…

如何通過AI進行數據資產梳理

最終產出 數據資產清單 包含所有數據資產的詳細目錄,列出數據集名稱、描述、所有者、格式、存儲位置和元數據。 用途:幫助政府部門清晰了解數據資產分布和狀態。 數據質量報告 數據質量評估結果,記錄準確性、完整性、一致性等問題及改進建議,基于政府認可的數據質量框架(如…

【傳奇開心果系列】Flet框架結合pillow實現的英文文字倒映特效自定義模板特色和實現原理深度解析

Flet框架結合pillow實現的英文文字倒映特效自定義模板特色和實現原理深度解析 一、效果展示截圖 二、使用場景 三、特色說明 四、概括說明 五、依賴文件列表 六、安裝依賴命令 七、 項目結構建議 八、注意事項 九、Flet 文字倒影效果實現原理分析 (一)組件結構與功能 1. 圖像…

2025最新深度學習面試必問100題--理論+框架+原理+實踐 (下篇)

2025最新深度學習面試必問100題–理論框架原理實踐 (下篇) 在上篇中&#xff0c;我們已經深入探討了機器學習基礎、CNN、RNN及其變體&#xff0c;以及模型優化的核心技巧。 在下篇中&#xff0c;我們將把目光投向更遠方&#xff0c;聚焦于當今AI領域最炙手可熱的前沿。我們將深…

原子工程用AC6編譯不過問題

…\Output\atk_h750.axf: Error: L6636E: Pre-processor step failed for ‘…\User\SCRIPT\qspi_code.scf.scf’修改前&#xff1a; #! armcc -E ;#! armclang -E --targetarm-arm-none-eabi -mcpucortex-m7 -xc /* 使用說明 ! armclang -E --targetarm-arm-none-eabi -mcpuco…

Python有哪些經典的常用庫?(第一期)

目錄 1、NumPy (數值計算基礎庫) 核心特點&#xff1a; 應用場景&#xff1a; 代碼示例&#xff1a; 2、Pandas (數據分析處理庫) 應用場景&#xff1a; 代碼示例&#xff1a; 3、Scikit-learn (機器學習庫) 核心特點&#xff1a; 應用場景&#xff1a; 代碼示例&am…

現代 C++ 高性能程序驅動器架構

&#x1f9e0; 現代 C 高性能程序驅動器架構M/PA&#xff08;多進程&#xff09;是隔離的“孤島”&#xff0c;M/TA&#xff08;多線程&#xff09;是共享的“戰場”&#xff0c;EDSM&#xff08;事件驅動&#xff09;是高效的“反應堆”&#xff0c;MDSM&#xff08;消息驅動&…

投資儲能項目能賺多少錢?小程序幫你測算

為解決電網負荷平衡、提升新能源消納等問題&#xff0c;儲能項目的投資開發越來越多。那么&#xff0c;投資儲能項目到底能賺多少錢&#xff1f;適不適合投資&#xff1f;用“綠蟲零碳助手”3秒鐘精準測算。操作只需四步&#xff0c;簡單易懂&#xff1a;1.快速登錄&#xff1a…

Mac 能夠連Wife,但是不能上網問題解決

請按照以下步驟從最簡單、最可能的原因開始嘗試&#xff1a; 第一步&#xff1a;基礎快速排查 這些步驟能解決大部分臨時性的小故障。 重啟設備&#xff1a;關閉您的 Mac 和路由器&#xff0c;等待一分鐘后再重新打開。這是解決網絡問題最有效的“萬能藥”。檢查其他設備&am…

基于SpringBoot的旅游管理系統的設計與實現(代碼+數據庫+LW)

摘要 本文闡述了一款基于SpringBoot框架的旅游管理系統設計與實現。該系統整合了用戶信息管理、旅游資源展示、訂單處理流程及安全保障機制等核心功能&#xff0c;專為提升旅游行業的服務質量和運營效率而設計。 系統采用前后端分離架構&#xff0c;前端界面設計注重跨設備兼…

Springboot樂家流浪貓管理系統16lxw(程序+源碼+數據庫+調試部署+開發環境)帶論文文檔1萬字以上,文末可獲取,系統界面在最后面。

系統程序文件列表項目功能&#xff1a;領養人,流浪貓,領養申請開題報告內容基于Spring Boot的樂家流浪貓管理系統開題報告一、研究背景與意義隨著城市化進程加速和人口增長&#xff0c;流浪貓問題已成為全球性社會挑戰。據統計&#xff0c;全球每年約有1.5億只無家可歸的寵物&a…

函數定義跳轉之代碼跳轉

相信大家在開發的過程中都有用到函數定義跳轉的功能&#xff0c;在 IDE 中&#xff0c;如果在函數調用的地方停留光標&#xff0c;可能會提示對應的函數定義&#xff0c;在 GitHub 中也是如此&#xff0c;對于一些倉庫來說&#xff0c;我們可以直接查看對應的函數定義了&#x…