<context-param> 标签引出的 web.xml 文件的加载顺序 [转]

代码示例 :

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>/WEB-INF/classes/log4j.properties</param-value>
    </context-param>

一 . 初始化过程

  1. 在启动 Web 项目时 , web 容器 ( 比如tomcat ) 会先读取 web.xml 文件中的两个节点 <listener> 和 <context-param>
  2. 接着容器会创建一个 ServletContext 对象 (也就是 servlet上下文 ), 这个 web 项目的所有部分都将共享这个上下文
  3. 在第一步读取的 <context-param> 信息 , 将会转换为键值对的形式 , 并交给 servletContext 对象(这些键值对,会被 listener , filter 等使用到 )
  4. 接着容器会创建 <listener> 注册的监听器对象

二 . servlet 启动顺序

      <load-on-startup> 元素在 web 应用启动的时候指定了 servlet 被加载的顺序,它的值必须是一个整数。如果它的值是一个负整数或是这个元素不存在,那么容器会在该 servlet 被调用的时候,加载这个 servlet 。如果值是正整数或零,容器在配置的时候就加载并初始化这个 servlet ,容器必须保证值小的先被加载。如果值相等,容器可以自动选择先加载谁。 在 servlet 的配置当中, <load-on-startup>5</load-on-startup> 的含义是: 标记容器是否在启动的时候就加载这个 servlet 。 当值为 0 或者大于 0 时,表示容器在应用启动时就加载这个 servlet ; 当是一个负数时或者没有指定时,则指示容器在该 servlet 被选择时才加载。 正数的值越小,启动该 servlet 的优先级越高

三 . 最终加载顺序

      在项目中总会遇到一些关于加载的优先级问题,近期也同样遇到过类似的,所以自己查找资料总结了下,下面有些是转载其他人的,毕竟人家写的不错,自己也就不重复造轮子了,只是略加点了自己的修饰。
      首先可以肯定的是,加载顺序与它们在 web.xml 文件中的先后顺序无关。即不会因为 filter 写在 listener 的前面而会先加载 filter 。最终得出的结论是: listener --> filter --> servlet
同时还存在着这样一种配置节: context-param ,它用于向 ServletContext 提供键值对,即应用程序上下文信息。我们的 listener, filter 等在初始化时会用到这些上下文中的信息,那么 context-param 配置节是不是应该写在 listener 配置节前呢?实际上 context-param 配置节可写在任意位置,因此真正的加载顺序为: context-param -> listener -> filter -> servlet
      对于某类配置节而言,与它们出现的顺序是有关的。以 filter 为例, web.xml 中当然可以定义多个 filter ,与 filter 相关的一个配置节是 filter-mapping ,这里一定要注意,对于拥有相同 filter-name 的 filter 和 filter-mapping 配置节而言, filter-mapping 必须出现在 filter 之后,否则当解析到 filter-mapping 时,它所对应的 filter-name 还未定义。 web 容器启动时初始化每个 filter 时,是按照 filter 配置节出现的顺序来初始化的,当请求资源匹配多个 filter-mapping 时, filter 拦截资源是按照 filter-mapping 配置节出现的顺序来依次调用 doFilter() 方法的。
      servlet 同 filter 类似 ,此处不再赘述。
      由此,可以看出, web.xml 的加载顺序是: context-param -> listener -> filter -> servlet ,而同个类型之间的实际程序调用的时候的顺序是根据对应的 mapping 的顺序进行调用的。

原文地址 : [ WEB容器启动之Web.xml加载顺序 ]

转载于:https://www.cnblogs.com/daimajun/p/7411071.html

package com.example.kucun2.entity.data; import android.util.Log; import androidx.annotation.NonNull; import com.example.kucun2.function.MyAppFnction; import com.google.gson.Gson; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.util.*; import okhttp3.*; /** * 支持数据变更自动同步的泛型集合类 * 移除了代理机制,改用基类的事件监听 */ public class SynchronizedList<T extends SynchronizableEntity> implements List<T> { private final List<T> list = new ArrayList<>(); private final OkHttpClient client = new OkHttpClient(); private static final Gson gson = new Gson(); private static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); // 批量操作模式标志 private boolean batchMode = false; // 实体变更监听器 private final EntityChangeListener<T> listener = entity -> { if (!batchMode) syncEntity(entity); }; // 直接接受Class参数的构造函数 public SynchronizedList(Class<T> T) {} // 添加监听器的方法 private void registerListener(T entity) { if (entity != null) { entity.addPropertyChangeListener(listener); } } private void unregisterListener(T entity) { if (entity != null) { entity.removePropertyChangeListener(listener); } } private String getUrl() { return MyAppFnction.getStringResource("string", "url"); } /** * 同步实体到后端 */ private static void syncEntity(SynchronizableEntity entity) { String operation = "update"; // 默认为更新操作 String endpoint = entity.getEndpoint(operation); String json = gson.toJson(entity); RequestBody body = RequestBody.create(json, JSON); Request request = new Request.Builder() .url(getUrl() + endpoint) .put(body) // 统一使用PUT方法 .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { Log.e("SynchronizedList", "同步失败: " + entity.getClass().getSimpleName(), e); } @Override public void onResponse(@NonNull Call call, @NonNull Response response) { if (!response.isSuccessful()) { Log.e("SynchronizedList", "同步失败: " + response.code()); } response.close(); } }); } // 批量操作方法 public void beginBatch() { this.batchMode = true; } public void endBatch() { this.batchMode = false; syncAllEntities(); } private void syncAllEntities() { for (T entity : list) { syncEntity(entity); } } // List接口实现(添加时注册监听器,移除时取消监听) @Override public boolean add(T t) { registerListener(t); boolean result = list.add(t); if (!batchMode) syncEntity(t); return result; } @Override public void add(int index, T element) { registerListener(element); list.add(index, element); if (!batchMode) syncEntity(element); } @Override public boolean remove(Object o) { if (o instanceof SynchronizableEntity) { T entity = (T) o; unregisterListener(entity); if (!batchMode) syncEntity(entity); } return list.remove(o); } @Override public T remove(int index) { T entity = list.get(index); if (entity != null) { unregisterListener(entity); if (!batchMode) syncEntity(entity); } return list.remove(index); } @Override public boolean addAll(@NonNull Collection<? extends T> c) { for (T t : c) registerListener(t); return list.addAll(c); } @Override public boolean removeAll(@NonNull Collection<?> c) { for (Object o : c) { if (o instanceof SynchronizableEntity) { unregisterListener((T) o); } } return list.removeAll(c); } @Override public boolean retainAll(@NonNull Collection<?> c) { return false; } @Override public void clear() { for (T t : list) { unregisterListener(t); if (!batchMode) syncEntity(t); } list.clear(); } @Override public T get(int index) { return list.get(index); } @Override public int size() { return list.size(); } @Override public boolean isEmpty() { return list.isEmpty(); } @Override public boolean contains(Object o) { return list.contains(o); } @NonNull @Override public Iterator<T> iterator() { return list.iterator(); } @NonNull @Override public Object[] toArray() { return list.toArray(); } @NonNull @Override public <T1> T1[] toArray(@NonNull T1[] a) { return list.toArray(a); } @Override public boolean containsAll(@NonNull Collection<?> c) { return list.containsAll(c); } @Override public boolean addAll(int index, @NonNull Collection<? extends T> c) { for (T element : c) { registerListener(element); } boolean result = list.addAll(index, c); if (!batchMode) { for (T element : c) { syncEntity(element); } } return result; } @Override public T set(int index, T element) { T oldElement = list.set(index, element); // 取消旧元素的监听 if (oldElement != null) { unregisterListener(oldElement); if (!batchMode) syncEntity(oldElement); } // 注册新元素的监听 registerListener(element); if (!batchMode) { syncEntity(element); } return oldElement; } @Override public int indexOf(Object o) { return list.indexOf(o); } @Override public int lastIndexOf(Object o) { return list.lastIndexOf(o); } @NonNull @Override public ListIterator<T> listIterator() { return list.listIterator(); } @NonNull @Override public ListIterator<T> listIterator(int index) { return list.listIterator(index); } @NonNull @Override public List<T> subList(int fromIndex, int toIndex) { // 创建子列表的同步包装器 return new SynchronizedListWrapper(list.subList(fromIndex, toIndex)); } // 其他List接口方法保持原样... // [此处省略未修改的List接口方法实现] /** * 实体变更监听接口 */ public interface EntityChangeListener<T> extends PropertyChangeListener { default void onPropertyChange(T entity, String propertyName, Object oldValue, Object newValue) { syncEntity((SynchronizableEntity) entity); } } // ==================== 内部辅助类 ==================== private class SynchronizedListWrapper implements List<T> { private final List<T> wrapped; public SynchronizedListWrapper(List<T> list) { this.wrapped = list; } @Override public boolean add(T element) { registerListener(element); boolean result = wrapped.add(element); if (!batchMode) syncEntity(element); return result; } @Override public void add(int index, T element) { registerListener(element); wrapped.add(index, element); if (!batchMode) syncEntity(element); } @Override public T set(int index, T element) { T oldElement = wrapped.get(index); if (oldElement != null) { unregisterListener(oldElement); if (!batchMode) syncEntity(oldElement); } registerListener(element); T result = wrapped.set(index, element); if (!batchMode) syncEntity(element); return result; } @Override public T remove(int index) { T element = wrapped.get(index); if (element != null) { unregisterListener(element); if (!batchMode) syncEntity(element); } return wrapped.remove(index); } @Override public boolean remove(Object o) { if (o instanceof SynchronizableEntity) { T entity = (T) o; if (wrapped.contains(entity)) { unregisterListener(entity); if (!batchMode) syncEntity(entity); } } return wrapped.remove(o); } @Override public boolean addAll(@NonNull Collection<? extends T> c) { for (T element : c) { registerListener(element); } boolean result = wrapped.addAll(c); if (!batchMode) { for (T element : c) { syncEntity(element); } } return result; } @Override public boolean addAll(int index, @NonNull Collection<? extends T> c) { for (T element : c) { registerListener(element); } boolean result = wrapped.addAll(index, c); if (!batchMode) { for (T element : c) { syncEntity(element); } } return result; } @Override public boolean removeAll(@NonNull Collection<?> c) { // 先取消所有要移除元素的监听器 for (Object o : c) { if (o instanceof SynchronizableEntity) { T entity = (T) o; if (wrapped.contains(entity)) { unregisterListener(entity); if (!batchMode) syncEntity(entity); } } } return wrapped.removeAll(c); } @Override public boolean retainAll(@NonNull Collection<?> c) { // 找出要移除的元素 List<T> toRemove = new ArrayList<>(); for (T element : wrapped) { if (!c.contains(element)) { toRemove.add(element); } } // 取消这些元素的监听器 for (T element : toRemove) { unregisterListener(element); if (!batchMode) syncEntity(element); } return wrapped.retainAll(c); } @Override public void clear() { // 取消所有元素的监听器 for (T element : wrapped) { unregisterListener(element); if (!batchMode) syncEntity(element); } wrapped.clear(); } // ---------------- 以下是非修改方法,直接委托给包装列表 ---------------- @Override public int size() { return wrapped.size(); } @Override public boolean isEmpty() { return wrapped.isEmpty(); } @Override public boolean contains(Object o) { return wrapped.contains(o); } @NonNull @Override public Iterator<T> iterator() { return wrapped.iterator(); } @NonNull @Override public Object[] toArray() { return wrapped.toArray(); } @NonNull @Override public <T1> T1[] toArray(@NonNull T1[] a) { return wrapped.toArray(a); } @Override public boolean containsAll(@NonNull Collection<?> c) { return wrapped.containsAll(c); } @Override public T get(int index) { return wrapped.get(index); } @Override public int indexOf(Object o) { return wrapped.indexOf(o); } @Override public int lastIndexOf(Object o) { return wrapped.lastIndexOf(o); } @NonNull @Override public ListIterator<T> listIterator() { return wrapped.listIterator(); } @NonNull @Override public ListIterator<T> listIterator(int index) { return wrapped.listIterator(index); } @NonNull @Override public List<T> subList(int fromIndex, int toIndex) { return new SynchronizedListWrapper(wrapped.subList(fromIndex, toIndex)); } } }package com.example.kucun2.entity.data; import android.content.Context; import android.util.Log; import com.example.kucun2.entity.*; import com.example.kucun2.function.MyAppFnction; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.*; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class Data { // 数据集合声明(保持原有属性名不变) public static SynchronizedList<Bancai> bancais = new SynchronizedList<>(Bancai.class); public static SynchronizedList<Caizhi> caizhis = new SynchronizedList<>(Caizhi.class); public static SynchronizedList<Mupi> mupis = new SynchronizedList<>(Mupi.class); public static SynchronizedList<Chanpin> chanpins = new SynchronizedList<>(Chanpin.class); public static SynchronizedList<Chanpin_Zujian> chanpinZujians = new SynchronizedList<>(Chanpin_Zujian.class); public static SynchronizedList<Dingdan> dingdans = new SynchronizedList<>(Dingdan.class); public static SynchronizedList<Dingdan_Chanpin> dingdanChanpins = new SynchronizedList<>(Dingdan_Chanpin.class); public static SynchronizedList<Dingdan_Bancai> dingdanBancais = new SynchronizedList<>(Dingdan_Bancai.class); public static SynchronizedList<Kucun> kucuns = new SynchronizedList<>(Kucun.class); public static SynchronizedList<Zujian> zujians = new SynchronizedList<>(Zujian.class); public static SynchronizedList<User> users = new SynchronizedList<>(User.class); public static SynchronizedList<Jinhuo> jinhuoList = new SynchronizedList<>(Jinhuo.class); private static final Gson gson = new Gson(); private static final OkHttpClient client = new OkHttpClient(); private static final String TAG = "DataLoader"; // 加载所有数据的方法 public static void loadAllData(Context context, LoadDataCallback callback) { // 开始批量模式 for (SynchronizedList<?> list : getAllSyncLists()) { list.beginBatch(); } String url = MyAppFnction.getStringResource("String", "url") + MyAppFnction.getStringResource("String", "url_all"); Request request = new Request.Builder() .url(url) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e(TAG, "Failed to load data", e); if (callback != null) callback.onFailure(); } @Override public void onResponse(Call call, Response response) throws IOException { parseAndAssignData(response.body().string(), context,callback); // 结束批量模式(会触发一次全量同步) for (SynchronizedList<?> list : getAllSyncLists()) { list.endBatch(); } if (callback != null) callback.onSuccess(); } }); } // 获取所有同步列表的方法 private static List<SynchronizedList<?>> getAllSyncLists() { return Arrays.asList(bancais, caizhis, mupis, chanpins, chanpinZujians, dingdans, dingdanChanpins, dingdanBancais, kucuns, zujians, users, jinhuoList); } // 解析并赋值数据 private static void parseAndAssignData(String jsonData, Context context, LoadDataCallback callback) { // 创建包含所有数据类型的TypeToken Type informationType = new TypeToken<Information<AllDataResponse>>(){}.getType(); Information<AllDataResponse> information = gson.fromJson(jsonData, informationType); if (information == null || information.getData() == null || information.getStatus() != 200) { Log.e(TAG, "Invalid response data"); if (callback != null) callback.onFailure(); return; } AllDataResponse allData = information.getData(); // 赋值到对应的列表(使用安全方法保持已有引用) updateList(bancais, allData.bancais); updateList(caizhis, allData.caizhis); updateList(mupis, allData.mupis); updateList(chanpins, allData.chanpins); updateList(chanpinZujians, allData.chanpin_zujians); updateList(dingdans, allData.dingdans); updateList(dingdanChanpins, allData.dingdan_chanpins); updateList(dingdanBancais, allData.dingdan_bancais); updateList(kucuns, allData.kucuns); updateList(zujians, allData.zujians); updateList(users, allData.users); updateList(jinhuoList, allData.jinhuos); resolveReferences(); if (callback != null) callback.onSuccess(); } // 更新列表内容但保持对象引用不变 private static <T> void updateList(List<T> existingList, List<T> newList) { if (newList == null) return; existingList.clear(); for (T item : newList) { existingList.add(item); // 会被包装成代理对象 } } // 解析对象间的引用关系(使用ID关联) // 改进的引用解析方法 private static void resolveReferences() { // 创建全局对象映射 Map<Integer, EntityClassGrassrootsid> globalMap = new HashMap<>(); // 填充全局映射表 addToGlobalMap(globalMap, bancais); addToGlobalMap(globalMap, caizhis); addToGlobalMap(globalMap, mupis); addToGlobalMap(globalMap, chanpins); addToGlobalMap(globalMap, zujians); addToGlobalMap(globalMap, dingdans); addToGlobalMap(globalMap, kucuns); addToGlobalMap(globalMap, users); // 解析所有实体的引用 resolveListReferences(bancais, globalMap); resolveListReferences(kucuns, globalMap); resolveListReferences(dingdanChanpins, globalMap); resolveListReferences(chanpinZujians, globalMap); resolveListReferences(dingdanBancais, globalMap); resolveListReferences(jinhuoList, globalMap); } // 添加对象到全局映射表 private static void addToGlobalMap(Map<Integer, EntityClassGrassrootsid> map, SynchronizedList<?> list) { for (Object item : list) { Object original = getOriginalFromList(list, item); if (original instanceof EntityClassGrassrootsid) { EntityClassGrassrootsid entity = (EntityClassGrassrootsid) original; map.put(entity.getId(), entity); } } } // 解析列表中所有实体的引用 private static void resolveListReferences(SynchronizedList<?> list, Map<Integer, EntityClassGrassrootsid> globalMap) { for (Object item : list) { Object original = getOriginalFromList(list, item); resolveEntityReferences(original, globalMap); } } // 安全获取原始对象(无需类型换) @SuppressWarnings("unchecked") private static <T> T getOriginalFromList(SynchronizedList<?> list, Object item) { try { // 使用反射调用getOriginal方法 Method getOriginal = list.getClass().getMethod("getOriginal", Object.class); return (T) getOriginal.invoke(list, item); } catch (Exception e) { Log.e("Data", "Failed to get original object", e); return (T) item; } } //解析集合中的引用 private static void resolveCollectionReferences(Collection<?> collection, Map<Integer, EntityClassGrassrootsid> globalMap) { for (Object item : collection) { if (item instanceof EntityClassGrassrootsid) { EntityClassGrassrootsid ref = (EntityClassGrassrootsid) item; EntityClassGrassrootsid resolved = globalMap.get(ref.getId()); if (resolved != null && ref.getClass().isInstance(resolved)) { // 注意:直接替换集合元素需要特殊处理 // 实际项目应考虑使用CopyOnWrite集合或创建新集合 if (collection instanceof List) { int index = ((List<?>) collection).indexOf(ref); if (index != -1) { try { ((List<Object>) collection).set(index, resolved); } catch (Exception e) { Log.e("Data", "Failed to update collection", e); } } } } } } } // 解析单个实体的引用 private static void resolveEntityReferences(Object entity, Map<Integer, EntityClassGrassrootsid> globalMap) { if (entity == null) return; // 如果是代理对象则获取原始对象 Object target = entity; if (entity instanceof SynchronizedList.IProxyHandler) { target = ((SynchronizedList.IProxyHandler) entity).getOriginal(); } Class<?> clazz = entity.getClass(); for (Field field : clazz.getDeclaredFields()) { try { field.setAccessible(true); // 处理基本类型字段 if (EntityClassGrassrootsid.class.isAssignableFrom(field.getType())) { EntityClassGrassrootsid ref = (EntityClassGrassrootsid) field.get(entity); if (ref != null && ref.getId() != null) { EntityClassGrassrootsid resolved = globalMap.get(ref.getId()); if (resolved != null && field.getType().isInstance(resolved)) { field.set(entity, resolved); } } } // 处理集合类型字段 else if (Collection.class.isAssignableFrom(field.getType())) { Collection<?> collection = (Collection<?>) field.get(entity); if (collection != null) { resolveCollectionReferences(collection, globalMap); } } } catch (Exception e) { Log.e("Data", "Error resolving field: " + field.getName(), e); } } } // 创建ID到对象的映射 private static <T extends EntityClassGrassrootsid> Map<Integer, T> createIdMap(List<T> list) { Map<Integer, T> map = new HashMap<>(); for (T item : list) { map.put(item.getId(), item); } return map; } // 回调接口 public interface LoadDataCallback { void onSuccess(); void onFailure(); } private static void resolveFields(Object entity, Map<Integer, EntityClassGrassrootsid> globalMap) { for (Field field : entity.getClass().getDeclaredFields()) { if (EntityClassGrassrootsid.class.isAssignableFrom(field.getType())) { try { field.setAccessible(true); EntityClassGrassrootsid ref = (EntityClassGrassrootsid) field.get(entity); if (ref != null) { EntityClassGrassrootsid resolved = globalMap.get(ref.getId()); if (resolved != null && field.getType().isAssignableFrom(resolved.getClass())) { field.set(entity, resolved); } } } catch (Exception e) { Log.e("Data", "Error resolving reference", e); } } } } // 内部类用于解析JSON响应 public static class AllDataResponse { public List<Bancai> bancais; public List<Caizhi> caizhis; public List<Mupi> mupis; public List<Chanpin> chanpins; public List<Chanpin_Zujian> chanpin_zujians; public List<Dingdan> dingdans; public List<Dingdan_Chanpin> dingdan_chanpins; public List<Dingdan_Bancai> dingdan_bancais; public List<Kucun> kucuns; public List<Zujian> zujians; public List<User> users; public List<Jinhuo> jinhuos; } }package com.example.kucun2.entity.data; import android.util.Log; import com.example.kucun2.function.MyAppFnction; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.HashMap; import java.util.Map; public abstract class SynchronizableEntity implements EntityClassGrassrootsid { // 使用标准属性变更支持类 private transient PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); // 属性缓存(可选,用于优化) private final Map<String, Object> propertyCache = new HashMap<>(); // 添加属性变更监听器 public void addPropertyChangeListener(PropertyChangeListener listener) { if (changeSupport == null) { changeSupport = new PropertyChangeSupport(this); } changeSupport.addPropertyChangeListener(listener); } // 移除属性变更监听器 public void removePropertyChangeListener(PropertyChangeListener listener) { if (changeSupport != null) { changeSupport.removePropertyChangeListener(listener); } } // 触发属性变更通知(子类在setter中调用) protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { if (changeSupport != null && changeSupport.hasListeners(propertyName)) { // 更新属性缓存(可选) propertyCache.put(propertyName, newValue); // 触发变更事件 changeSupport.firePropertyChange(propertyName, oldValue, newValue); } } // 获取属性当前值(可选,用于setter中获取旧值) protected <T> T getCachedProperty(String propertyName) { return (T) propertyCache.get(propertyName); } // 初始化属性缓存(可选,在构造函数中调用) protected void initPropertyCache() { // 初始化所有字段到缓存 for (java.lang.reflect.Field field : getClass().getDeclaredFields()) { try { field.setAccessible(true); propertyCache.put(field.getName(), field.get(this)); } catch (Exception e) { // 忽略初始化异常 } } } /** * 添加url * @param type 操作增删改查 * @return 返回相对url */ public String getEndpoint(String type){ //从String.xml获取url Log.d("getEndpoint", "getEndpoint: "+"url_"+type+"_"+this.getClass().getSimpleName().toLowerCase()); return MyAppFnction.getStringResource("string","url_"+type+"_"+this.getClass().getSimpleName().toLowerCase()); } public void sync(){ } }
06-11
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值