当前位置: 首页 > news >正文

Day47

Day47

手写Spring-MVC之DispatcherServlet

DispatcherServlet的思路:

前端传来URI,在TypeContainer容器类中通过uri得到对应的类描述类对象(注意:在监听器封装类描述类对象的时候,是针对于每一个URI进行封装的,也就是说每一个方法(uri)都有一个类描述类),通过类描述类对象获得类对象和方法描述类对象,再通过方法描述类对象获得方法对象和参数描述类对象。对参数描述类对象进行处理获得参数数组,然后通过method.invoke()执行方法,并返回值,最后处理返回值。

public class DispatherServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doPost(request, response);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {request.setCharacterEncoding("UTF-8");response.setContentType("text/html;charSet=UTF-8");String uri = request.getRequestURI();HashMap<String, BeanDefinition> maps = TypeContainer.getMaps();BeanDefinition beanDefinition = maps.get(uri);if(beanDefinition==null){//System.out.println("没找到该uri下的类描述类");throw new FrameWorkException(ResponseCode.REQUEST_PATH_EXCEPTION.getCode(),ResponseCode.REQUEST_PATH_EXCEPTION.getMessage());}Object t = beanDefinition.getT();MethodDefinition methodDefinition = beanDefinition.getMethodDefinition();Method method = methodDefinition.getMethod();method.setAccessible(true);Model model = new Model();List<ParameterDefinition> parameterDefinitions = methodDefinition.getParameterDefinitions();Object[] args = handlerParameterArgs(parameterDefinitions, request, response,model);try{//调用Controller层里的某个方法Object returnVal = method.invoke(t, args);if(returnVal!=null){//处理返回值handlerReturnVal(methodDefinition,returnVal,request,response,model);}}catch (Exception e){System.out.println("出现异常了,处理全局异常");}}public void handlerReturnVal(MethodDefinition methodDefinition,Object returnVal,HttpServletRequest request,HttpServletResponse response,Model model) throws ServletException, IOException {if(methodDefinition.isResponseBodyHasOrNot()){//返回数据为JSON格式String jsonString = JSON.toJSONString(returnVal);sendResponse(response,jsonString);}else if(returnVal.getClass()==String.class){//如果返回值是字符串,先添加请求数据,再跳转(转发)handlerRequestVal(request,model.getMaps());jumpPage(returnVal,request,response);} else if (returnVal.getClass() == ModelAndView.class) {//如果返回值是ModelAndView,添加请求后跳转(转发)ModelAndView modelAndView = (ModelAndView) returnVal;handlerRequestVal(request,modelAndView.getMaps());jumpPage(modelAndView.getViewName(),request,response);}}public void sendResponse(HttpServletResponse response,String jsonString) throws IOException {response.getWriter().write(jsonString);}public void handlerRequestVal(HttpServletRequest request,Map<String,Object> maps){Set<Map.Entry<String, Object>> entries = maps.entrySet();for (Map.Entry<String, Object> entry : entries) {String key = entry.getKey();Object value = entry.getValue();request.setAttribute(key,value);}}public void jumpPage(Object returnVal,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {String str = (String) returnVal;request.getRequestDispatcher(str).forward(request,response);}public Object[] handlerParameterArgs(List<ParameterDefinition> parameterDefinitions,HttpServletRequest request,HttpServletResponse response,Model model){if(parameterDefinitions==null){return null;}Object[] args = new Object[parameterDefinitions.size()];for (ParameterDefinition parameterDefinition : parameterDefinitions) {Class<?> clazz = parameterDefinition.getClazz();//获取参数的class对象String name = parameterDefinition.getName();//获取参数的名字int index = parameterDefinition.getIndex();//获取参数的下标if(judgeTypeIsJavaOrNot(clazz)){//判断是否是Java常用数据类型args = handlerJavaType(request, name, clazz, args, index);} else if (clazz==HttpServletRequest.class) {//判断是否是请求类型args[index] = request;} else if (clazz==HttpServletResponse.class) {//判断是否是响应类型args[index] = response;} else if (clazz == String[].class) {//判断是否是数组类型String[] arr = request.getParameterValues(name);args[index] = arr;} else if (clazz == List.class) {//判断是否是List集合类型handlerListType(request,parameterDefinition,args,index);} else if (clazz== Model.class) {//判断是否是Model类型args[index] = model;}else{//判断是否是自定义类型handlerOtherType(parameterDefinition,request,args,index);}}return args;}public void handlerOtherType(ParameterDefinition parameterDefinition,HttpServletRequest request,Object[] args,int index){try {if(parameterDefinition.isRequestBodyHasOrNot()){//如果返回的是JSON格式数据BufferedReader br = request.getReader();StringBuffer sb = new StringBuffer();char[] cs = new char[1024];int len;while((len= br.read(cs))!=-1){sb.append(cs,0,len);}String jsonStr = sb.toString();Object obj = JSON.parseObject(jsonStr,parameterDefinition.getClazz());args[index] = obj;}else{Object obj = parameterDefinition.getClazz().newInstance();Map<String, String[]> parameterMap = request.getParameterMap();BeanUtils.populate(obj,parameterMap);args[index] = obj;}} catch (InstantiationException | IllegalAccessException | InvocationTargetException | IOException e) {throw new RuntimeException(e);}}public void handlerListType(HttpServletRequest request,ParameterDefinition parameterDefinition,Object[] args,int index){Map<String, String[]> parameterMap = request.getParameterMap();//key --- Value//user[0].username --- new String[]{"pengyuyan"};//user[0].password --- new String[]{"123123"};//user[1].username --- new String[]{"wuyanzu"};//user[1].password --- new String[]{"123456"};//获取泛型类型Type pt = parameterDefinition.getActualTypeArguments()[0];String className = pt.getTypeName();Class<?> aClass = null;try {aClass = Class.forName(className);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}ArrayList<Object> list = new ArrayList<>();Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();for (Map.Entry<String, String[]> entry : entries) {String key = entry.getKey();//user[0].usernameString[] value = entry.getValue();//new String[]{"guodan"}int i = Integer.parseInt(key.substring(key.indexOf("[") + 1, key.indexOf("]")));//集合中的下标String fieldName = key.substring(key.indexOf(".") + 1);//集合中元素对象的属性名String fieldValue = value[0];//获取集合中元素对象里的属性值Object o =null;try{o = list.get(i);}catch (IndexOutOfBoundsException e){//该集合下标上没有元素try {o = aClass.newInstance();//创建对象list.add(o);//添加对象} catch (InstantiationException | IllegalAccessException ex) {throw new RuntimeException(ex);}}try {BeanUtils.setProperty(o,fieldName,fieldValue);//设置对象属性} catch (IllegalAccessException | InvocationTargetException ex) {throw new RuntimeException(ex);}args[index] = list;}}public Object[] handlerJavaType(HttpServletRequest request,String name,Class<?> clazz,Object[] args,int index){String parameter = request.getParameter(name);if(clazz==byte.class || clazz==Byte.class){args[index] =  Byte.parseByte(parameter);}else if(clazz==short.class || clazz==Short.class){args[index] = Short.parseShort(parameter);}else if(clazz==int.class || clazz==Integer.class){args[index] = Integer.parseInt(parameter);}else if(clazz==long.class || clazz==Long.class){args[index] = Long.parseLong(parameter);}else if(clazz==float.class || clazz==Float.class){args[index] = Float.parseFloat(parameter);}else if(clazz==double.class || clazz==Double.class){args[index] = Double.parseDouble(parameter);}else if(clazz==char.class || clazz==Character.class){args[index] = parameter.toCharArray()[0];}else if(clazz==boolean.class || clazz==Boolean.class){args[index] = Boolean.parseBoolean(parameter);}if(clazz==String.class){args[index] = parameter;}return args;}public boolean judgeTypeIsJavaOrNot(Class<?> clazz){if(clazz==byte.class || clazz==Byte.class || clazz==short.class || clazz==Short.class || clazz==int.class || clazz== Integer.class || clazz==long.class || clazz== Long.class || clazz==float.class || clazz== Float.class || clazz==double.class || clazz==Double.class || clazz==char.class || clazz== Character.class || clazz==boolean.class || clazz==Boolean.class || clazz==String.class){return true;}return false;}}

其中的细节和难点:

1.处理参数

处理参数的思路

获取参数的class对象、参数的名字、参数的下标。然后根据参数名利用请求从前端获取对应的参数对象并添加到参数数组args[]中,位置为对应下标的位置。

其中需要根据不同的参数类型分别进行处理

如果是Java常用数据类型(8大基本数据类型+string),则进行装箱处理(String 直接添加),然后添加到参数数组args[]中;

public Object[] handlerJavaType(HttpServletRequest request,String name,Class<?> clazz,Object[] args,int index){String parameter = request.getParameter(name);if(clazz==byte.class || clazz==Byte.class){args[index] =  Byte.parseByte(parameter);}else if(clazz==short.class || clazz==Short.class){args[index] = Short.parseShort(parameter);}else if(clazz==int.class || clazz==Integer.class){args[index] = Integer.parseInt(parameter);}else if(clazz==long.class || clazz==Long.class){args[index] = Long.parseLong(parameter);}else if(clazz==float.class || clazz==Float.class){args[index] = Float.parseFloat(parameter);}else if(clazz==double.class || clazz==Double.class){args[index] = Double.parseDouble(parameter);}else if(clazz==char.class || clazz==Character.class){args[index] = parameter.toCharArray()[0];}else if(clazz==boolean.class || clazz==Boolean.class){args[index] = Boolean.parseBoolean(parameter);}if(clazz==String.class){args[index] = parameter;}return args;}public boolean judgeTypeIsJavaOrNot(Class<?> clazz){if(clazz==byte.class || clazz==Byte.class || clazz==short.class || clazz==Short.class || clazz==int.class || clazz== Integer.class || clazz==long.class || clazz== Long.class || clazz==float.class || clazz== Float.class || clazz==double.class || clazz==Double.class || clazz==char.class || clazz== Character.class || clazz==boolean.class || clazz==Boolean.class || clazz==String.class){return true;}return false;}

如果是请求/响应类型的数据则直接添加到数组中;

如果是数组,则获取的是数组对象,并添加到args[]中;

String[] arr = request.getParameterValues(name);
args[index] = arr;

如果是List集合类型(前端设计页面的时候会规范返回的格式),则要先通过获取泛型(这里需要回到参数描述类中添加泛型类型这一属性)来获取集合里面元素的类型,然后获得传入的数据map,遍历每行数据拼接出元素的名字、下标和属性值。然后通过元素的类型利用反射创建类对象,创建List集合并添加对象,设置对象属性值(注意:这里有一个小技巧,利用try-catch先获取空集合里面的元素,在出现没有元素的异常后在catch中创建对象,并添加到list中。这样在同一个元素下标中的list位置就有了对象,就不会进入到异常处理中创建对象,而是在后续进行属性设置,直到下标改变再重新创建对象。),最后将List集合添加到args[]中;

参数类型描述类添加泛型属性(相应的需要修改监听器封装部分的代码):

@NoArgsConstructor
@AllArgsConstructor
@Data
public class ParameterDefinition {private String name;//参数名private Class<?> clazz;//参数类型private int index;//参数下标private Type[] actualTypeArguments;//参数泛型的数组}

监听器相关部分:

if(parameters.length!=0){for (int i = 0; i < parameters.length; i++) {//获取参数上泛型数组Type parameterizedType = parameters[i].getParameterizedType();Type[] actualTypeArguments = null;try{ParameterizedType pt = (ParameterizedType) parameterizedType;if(pt!=null){actualTypeArguments = pt.getActualTypeArguments();}}catch (Exception e){}String parameterName = parameters[i].getName();//获取参数名Class<?> parameterType = parameters[i].getType();//参数类型int index = i;//下标ParameterDefinition parameterDefinition = new ParameterDefinition(parameterName, parameterType, index,actualTypeArguments);//封装参数描述类对象parameterList.add(parameterDefinition);}
}

DispatcherServlet:

 public void handlerListType(HttpServletRequest request,ParameterDefinition parameterDefinition,Object[] args,int index){Map<String, String[]> parameterMap = request.getParameterMap();//key --- Value//user[0].username --- new String[]{"pengyuyan"};//user[0].password --- new String[]{"123123"};//user[1].username --- new String[]{"wuyanzu"};//user[1].password --- new String[]{"123456"};//获取泛型类型Type pt = parameterDefinition.getActualTypeArguments()[0];String className = pt.getTypeName();Class<?> aClass = null;try {aClass = Class.forName(className);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}ArrayList<Object> list = new ArrayList<>();Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();for (Map.Entry<String, String[]> entry : entries) {String key = entry.getKey();//user[0].usernameString[] value = entry.getValue();//new String[]{"guodan"}int i = Integer.parseInt(key.substring(key.indexOf("[") + 1, key.indexOf("]")));//集合中的下标String fieldName = key.substring(key.indexOf(".") + 1);//集合中元素对象的属性名String fieldValue = value[0];//获取集合中元素对象里的属性值Object o =null;try{o = list.get(i);}catch (IndexOutOfBoundsException e){//该集合下标上没有元素try {o = aClass.newInstance();//创建对象list.add(o);//添加对象} catch (InstantiationException | IllegalAccessException ex) {throw new RuntimeException(ex);}try {BeanUtils.setProperty(o,fieldName,fieldValue);//设置对象属性} catch (IllegalAccessException | InvocationTargetException ex) {throw new RuntimeException(ex);}args[index] = list;}}}

如果是Model类型(model需要创建,属性只有一个map,用来存放属性的名和值,同时有一个setAttribute()方法,即添加键值。model是用来跳转页面的,将前端的数据封装到自己的容器属性中,然后通过请求传递给前端。同时创建一个ModelAndView类,除了容器属性外还有一个字符串类型的路径,功能和model类似,但适用于需要动态指定跳转页面的复杂业务),则直接添加到args[]中;

public class Model {private Map<String,Object> maps= new ConcurrentHashMap<>();public void setAttributes(String key,Object value){maps.put(key,value);}public Map<String, Object> getMaps() {return maps;}
}public class ModelAndView {private Map<String,Object> maps = new HashMap<>();private String viewName;public void setAttributes(String key,Object value){maps.put(key,value);}public Map<String, Object> getMaps() {return maps;}public void setMaps(Map<String, Object> maps) {this.maps = maps;}public String getViewName() {return viewName;}public void setViewName(String viewName) {this.viewName = viewName;}
}

如果是自定义类型,则用反射创建对象,将数据利用BeanUtils工具类的populate方法将参数整合到类对象中,并添加到args[]中。

public void handlerOtherType(Class<?> clazz,HttpServletRequest request,Object[] args,int index){try {Object obj = clazz.newInstance();Map<String, String[]> parameterMap = request.getParameterMap();BeanUtils.populate(obj,parameterMap);args[index] = obj;} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}}
2.处理返回值

思路

如果返回值不为空,即该方法有返回值,则要对返回值的类型进行判断,然后添加请求数据,再进行转发。

不同类型返回值的处理:

这里返回值有两种情况,本质是跳转逻辑的不同。一种是字符串类型的路径,controller层会将前端的数据封装到Model对象中,servlet就获取model的map属性,遍历里面的数据利用请求设置到页面,而返回的字符串路径直接用来跳转,一种是ModelAndView类型的对象,同样遍历map属性,将属性通过请求设置到页面,并利用类里面的路径属性进行跳转。

public void handlerReturnVal(Object returnVal,HttpServletRequest request,HttpServletResponse response,Model model) throws ServletException, IOException {if(returnVal.getClass()==String.class){//如果返回值是字符串,先添加请求数据,再跳转(转发)handlerRequestVal(request,model.getMaps());jumpPage(returnVal,request,response);} else if (returnVal.getClass() == ModelAndView.class) {//如果返回值是ModelAndView,添加请求后跳转(转发)ModelAndView modelAndView = (ModelAndView) returnVal;handlerRequestVal(request,modelAndView.getMaps());jumpPage(modelAndView.getViewName(),request,response);}}//遍历Model/ModelAndView类对象的容器属性,把属性数据添加到请求public void handlerRequestVal(HttpServletRequest request,Map<String,Object> maps){Set<Map.Entry<String, Object>> entries = maps.entrySet();for (Map.Entry<String, Object> entry : entries) {String key = entry.getKey();Object value = entry.getValue();request.setAttribute(key,value);}}//页面跳转public void jumpPage(Object returnVal,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {String str = (String) returnVal;request.getRequestDispatcher(str).forward(request,response);}
3.处理JSON数据

如果接收的是JSON格式数据,那么如何识别数据以及怎样传递数据呢?

**思路:**参数注解用来识别判断客户端传递过来的数据是否是JSON数据,需要将JSON格式的字符串解析成参数类型的数据;方法注解表示服务器传输过去的数据为JSON数据,需要将返回值解析成JSON格式的字符串。

具体实现:添加RequestBody注解用在参数上,和ResponseBody注解用在方法上。

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestBody {
}@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseBody {
}

DispatcherServlet中在创建参数数组的时候判断参数上是否有注解,所以要在参数描述类中添加用来判断是否参数有注解的布尔值。在方法描述类中添加布尔值判断方法上是否有注解。(相应的要修改监听器中封装类的那部分代码)

参数描述类和方法描述类修改:

@NoArgsConstructor
@AllArgsConstructor
@Data
public class ParameterDefinition {private String name;//参数名private Class<?> clazz;//参数类型private int index;//参数下标private Type[] actualTypeArguments;//参数泛型的数组private boolean requestBodyHasOrNot;//参数上是否有@RequestBody注解}@NoArgsConstructor
@AllArgsConstructor
@Data
public class MethodDefinition {private String requestMappingPath;//子级URiprivate String name;//方法名private Method method;//方法对象private Class<?> returnClazz;//返回值类型private List<ParameterDefinition> parameterDefinitions;//参数描述类对象的集合private boolean responseBodyHasOrNot;//方法上是否有@ResponseBody注解
}

监听器相关部分:

if(parameters.length!=0){for (int i = 0; i < parameters.length; i++) {//获取参数上泛型数组Type parameterizedType = parameters[i].getParameterizedType();Type[] actualTypeArguments = null;try{ParameterizedType pt = (ParameterizedType) parameterizedType;if(pt!=null){actualTypeArguments = pt.getActualTypeArguments();}}catch (Exception e){}String parameterName = parameters[i].getName();//获取参数名Class<?> parameterType = parameters[i].getType();//参数类型int index = i;//下标//获取参数上是否有@RequestBody注解boolean requestBodyHasOrNot = false;RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);if(requestBody!=null){requestBodyHasOrNot = true;}ParameterDefinition parameterDefinition = new ParameterDefinition(parameterName, parameterType, index,actualTypeArguments,requestBodyHasOrNot);//封装参数描述类对象parameterList.add(parameterDefinition);}
}//获取方法上是否有@ResponseBody注解
boolean responseBodyHasOrNot = false;
ResponseBody responseBody = method.getAnnotation(ResponseBody.class);
if(responseBody!=null){responseBodyHasOrNot = true;
}
MethodDefinition methodDefinition = new MethodDefinition(sonUri, methodName, method, returnType, parameterList,responseBodyHasOrNot);//封装方法描述类对象

servlet中在自定义类型中要判断是否有对应的注解,如果有就解析json数据封装成对象然后添加到数组。

public void handlerOtherType(ParameterDefinition parameterDefinition,HttpServletRequest request,Object[] args,int index){try {if(parameterDefinition.isRequestBodyHasOrNot()){//如果返回的是JSON格式数据BufferedReader br = request.getReader();StringBuffer sb = new StringBuffer();char[] cs = new char[1024];int len;while((len= br.read(cs))!=-1){sb.append(cs,0,len);}String jsonStr = sb.toString();Object obj = JSON.parseObject(jsonStr,parameterDefinition.getClazz());args[index] = obj;}else{Object obj = parameterDefinition.getClazz().newInstance();Map<String, String[]> parameterMap = request.getParameterMap();BeanUtils.populate(obj,parameterMap);args[index] = obj;}} catch (InstantiationException | IllegalAccessException | InvocationTargetException | IOException e) {throw new RuntimeException(e);}
}

在方法处理中判断是否有对应的方法注解,如果有就将返回的JSON转换为字符串写回前端。

public void handlerReturnVal(MethodDefinition methodDefinition,Object returnVal,HttpServletRequest request,HttpServletResponse response,Model model) throws ServletException, IOException {if(methodDefinition.isResponseBodyHasOrNot()){//返回数据为JSON格式String jsonString = JSON.toJSONString(returnVal);sendResponse(response,jsonString);}else if(returnVal.getClass()==String.class){//如果返回值是字符串,先添加请求数据,再跳转(转发)handlerRequestVal(request,model.getMaps());jumpPage(returnVal,request,response);} else if (returnVal.getClass() == ModelAndView.class) {//如果返回值是ModelAndView,添加请求后跳转(转发)ModelAndView modelAndView = (ModelAndView) returnVal;handlerRequestVal(request,modelAndView.getMaps());jumpPage(modelAndView.getViewName(),request,response);}}public void sendResponse(HttpServletResponse response,String jsonString) throws IOException {response.getWriter().write(jsonString);}
注意:

1.@WebServlet不要加,在web项目中配置。因为是在项目中使用到,而不是全局。

postman

代替浏览器发送请求的工具

请求里面输入url,body选择raw->JSON,发送一个JSON格式的字符串

测试能否成功。

相关文章:

  • 深度学习原理与Pytorch实战
  • LabVIEW与PLC通讯方式及比较
  • python selenium 打开网页
  • Unity解决报错:Execution failed for task ‘:unityLibrary:BuildIl2CppTask‘
  • 淘客返利平台的API设计与安全
  • 在postgrel中使用hints
  • 等保2.0安全计算环境解读
  • 【0299】Postgres内核之哈希表(Hash Tables)
  • FIO压测磁盘性能以及需要注意的问题
  • 下标引用操作符;函数调用操作符;结构成员访问操作符
  • MySQL-核心知识要点
  • 基于大语言模型的本地知识库问答(离线部署)
  • vue3开发过程中遇到的一些问题记录
  • C++ 彻底搞懂指针(1)
  • golang 1.22特性之for loop
  • 0x05 Python数据分析,Anaconda八斩刀
  • 10个确保微服务与容器安全的最佳实践
  • CentOS 7 防火墙操作
  • iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  • Vue2.x学习三:事件处理生命周期钩子
  • 大主子表关联的性能优化方法
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 浏览器缓存机制分析
  • 事件委托的小应用
  • ionic异常记录
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • 昨天1024程序员节,我故意写了个死循环~
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (delphi11最新学习资料) Object Pascal 学习笔记---第13章第6节 (嵌套的Finally代码块)
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (分类)KNN算法- 参数调优
  • (排序详解之 堆排序)
  • (算法)Travel Information Center
  • (微服务实战)预付卡平台支付交易系统卡充值业务流程设计
  • (一)RocketMQ初步认识
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • .bat文件调用java类的main方法
  • .NET Framework 4.6.2改进了WPF和安全性
  • .net framework4与其client profile版本的区别
  • .net MVC中使用angularJs刷新页面数据列表
  • .NET Project Open Day(2011.11.13)
  • .Net(C#)自定义WinForm控件之小结篇
  • /bin/rm: 参数列表过长"的解决办法
  • [20150629]简单的加密连接.txt
  • [AIGC] 如何建立和优化你的工作流?
  • [Android]使用Retrofit进行网络请求
  • [bzoj1038][ZJOI2008]瞭望塔
  • [C#]C# winform部署yolov8目标检测的openvino模型
  • [C][栈帧]详细讲解
  • [CF543A]/[CF544C]Writing Code
  • [docker] Docker的私有仓库部署——Harbor
  • [GN] DP学习笔记板子