3 手写实现SpringMVC,第三节:通过反射给属性和参数注入值

本文主要是介绍3 手写实现SpringMVC,第三节:通过反射给属性和参数注入值,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在上一篇已经完成了读取beanName->Object映射关系的功能,这一篇就是把读取到的映射注入到属性中。

在WebController里定义了需要被Autowired的两个Service,myQueryService和modifyService,下面来给他们赋值。

通过反射给属性赋值

/*** 给被AutoWired注解的属性注入值*/private void doAutoWired() {if (instanceMapping.isEmpty()) {return;}//遍历所有被托管的对象for (Map.Entry<String, Object> entry : instanceMapping.entrySet()) {//查找所有被Autowired注解的属性// getFields()获得某个类的所有的公共(public)的字段,包括父类;// getDeclaredFields()获得某个类的所有申明的字段,即包括public、private和proteced,但是不包括父类的申明字段。Field[] fields = entry.getValue().getClass().getDeclaredFields();for (Field field : fields) {//没加autowired的不需要注值if (!field.isAnnotationPresent(Autowired.class)) {continue;}String beanName;//获取AutoWired上面写的值,譬如@Autowired("abc")Autowired autowired = field.getAnnotation(Autowired.class);if ("".equals(autowired.value())) {//例 searchService。注意,此处是获取属性的类名的首字母小写,与属性名无关,可以定义@Autowired SearchService abc都可以。beanName = lowerFirstChar(field.getType().getSimpleName());} else {beanName = autowired.value();}//将私有化的属性设为true,不然访问不到field.setAccessible(true);//去映射中找是否存在该beanName对应的实例对象if (instanceMapping.get(beanName) != null) {try {field.set(entry.getValue(), instanceMapping.get(beanName));} catch (IllegalAccessException e) {e.printStackTrace();}}}}}

在init方法里,instance下面加上doAutowired方法。
重启,查看注入情况。

可以看到webController里的属性,queryService和modifyService都已经被成功注入了正确的实现类。

建立Url到方法的映射

当Controller里的属性被注入值后,Service是可以使用了,但是访问Url时,系统依旧不知道该调用哪个方法来处理请求。
所以当我们请求某个url,如/web/add时,我们需要建立一个Url到Method的映射,这样才能访问到该方法并处理。这个地方也是SpringMVC区别于Struts2的巨大地方,Struts2是建立url到Controller类的映射,类里的成员变量是所有方法共享的,无论具体哪个方法都可以访问成员变量,这样会无形中浪费内存空间。而SpringMVC是建立的请求到方法的映射,与成员变量无关。
那么如何建立Url到方法的映射呢?这里就需要用上@RequestMapping注解了,由它来决定映射。
创建个map
private Map<String, Method> handlerMapping = new HashMap<>();
创建方法
/*** 建立url到方法的映射*/private void doHandlerMapping() {if (instanceMapping.isEmpty()) {return;}//遍历托管的对象,寻找Controllerfor (Map.Entry<String, Object> entry : instanceMapping.entrySet()) {Class<?> clazz = entry.getValue().getClass();//只处理Controller的,只有Controller有RequestMappingif (!clazz.isAnnotationPresent(Controller.class)) {continue;}//定义urlString url = "/";//取到Controller上的RequestMapping值if (clazz.isAnnotationPresent(RequestMapping.class)) {RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);url += requestMapping.value();}//获取方法上的RequestMappingMethod[] methods = clazz.getMethods();//只处理带RequestMapping的方法for (Method method : methods) {if (!method.isAnnotationPresent(RequestMapping.class)) {continue;}RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);//requestMapping.value()即是在requestMapping上注解的请求地址,不管用户写不写"/",我们都给他补上String realUrl = url + "/" + methodMapping.value();//替换掉多余的"/",因为有的用户在RequestMapping上写"/xxx/xx",有的不写,所以我们处理掉多余的"/"realUrl = realUrl.replaceAll("/+", "/");handlerMapping.put(realUrl, method);}}}
通过这个方法就能得到一个HashMap,key为RequestMapping上配置的url地址,value为Method对象。
重启看效果:


理论上来说我们能根据请求的Url,找到对应的需要执行的Method,就已经可以执行method.invoke去调用该方法了。
在doPost方法中,我们通过遍历HandlerMapping,寻找key等于req.getRequestURI()的Method,然后invoke。

但是在实际操作中,发现了一个问题,就是method.invoke(Object object, Object... args)方法,它需要两个参数,第一个Object是该Method所在的类实例,也就是我们的WebController类的实例,目前是存放在instanceMapping中key为webController的值。至于Object...参数则是该方法的所有参数,也就是@RequestParam("name") String name, HttpServletRequest request, HttpServletResponse response这几个。
但是在我们的上一步操作中,我们的HandlerMapping里只保存了method对象,没有保存Controller对象和所有的参数,所有这一步是执行不下去的。
那么就需要对HandlerMapping进行改造,把需要的值也放进去。
新建一个javaBean,来装载Method需要的所有属性
private class HandlerModel {Method method;Object controller;Map<String, Integer> paramMap;public HandlerModel(Method method, Object controller, Map<String, Integer> paramMap) {this.method = method;this.controller = controller;this.paramMap = paramMap;}}

添加doHandlerMapping方法,来完成Url到方法的映射
/*** 建立url到方法的映射*/private void doHandlerMapping() {if (instanceMapping.isEmpty()) {return;}//遍历托管的对象,寻找Controllerfor (Map.Entry<String, Object> entry : instanceMapping.entrySet()) {Class<?> clazz = entry.getValue().getClass();//只处理Controller的,只有Controller有RequestMappingif (!clazz.isAnnotationPresent(Controller.class)) {continue;}//定义urlString url = "/";//取到Controller上的RequestMapping值if (clazz.isAnnotationPresent(RequestMapping.class)) {RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);url += requestMapping.value();}//获取方法上的RequestMappingMethod[] methods = clazz.getMethods();//只处理带RequestMapping的方法for (Method method : methods) {if (!method.isAnnotationPresent(RequestMapping.class)) {continue;}RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);//requestMapping.value()即是在requestMapping上注解的请求地址,不管用户写不写"/",我们都给他补上String realUrl = url + "/" + methodMapping.value();//替换掉多余的"/",因为有的用户在RequestMapping上写"/xxx/xx",有的不写,所以我们处理掉多余的"/"realUrl = realUrl.replaceAll("/+", "/");//获取所有的参数的注解,有几个参数就有几个annotation[],为毛是数组呢,因为一个参数可以有多个注解……Annotation[][] annotations = method.getParameterAnnotations();//由于后面的Method的invoke时,需要传入所有参数的值的数组,所以需要保存各参数的位置/*以Search方法的这几个参数为例 @RequestParam("name") String name, HttpServletRequest request, HttpServletResponse response未来在invoke时,需要传入类似这样的一个数组["abc", request, response]。"abc"即是在Post方法中通过request.getParameter("name")来获取Request和response这个简单,在post方法中直接就有。所以我们需要保存@RequestParam上的value值,和它的位置。譬如 name->0,只有拿到了这两个值,才能将post中通过request.getParameter("name")得到的值放在参数数组的第0个位置。同理,也需要保存request的位置1,response的位置2*/Map<String, Integer> paramMap = new HashMap<>();//获取方法里的所有参数的参数名(注意:此处使用了ASM.jar 版本为asm-3.3.1,需要在web-inf下建lib文件夹,引入asm-3.3.1.jar,自行下载)//如Controller的add方法,将得到如下数组["name", "addr", "request", "response"]String[] paramNames = Play.getMethodParameterNamesByAsm4(clazz, method);//获取所有参数的类型,提取Request和Response的索引Class<?>[] paramTypes = method.getParameterTypes();for (int i = 0; i < annotations.length; i++) {//获取每个参数上的所有注解Annotation[] anns = annotations[i];if (anns.length == 0) {//如果没有注解,则是如String abc,Request request这种,没写注解的//如果没被RequestParam注解// 如果是Request或者Response,就直接用类名作key;如果是普通属性,就用属性名Class<?> type = paramTypes[i];if (type == HttpServletRequest.class || type == HttpServletResponse.class) {paramMap.put(type.getName(), i);} else {//参数没写@RequestParam注解,只写了String name,那么通过java是无法获取到name这个属性名的//通过上面asm获取的paramNames来映射paramMap.put(paramNames[i], i);}continue;}//有注解,就遍历每个参数上的所有注解for (Annotation ans : anns) {//找到被RequestParam注解的参数,并取value值if (ans.annotationType() == RequestParam.class) {//也就是@RequestParam("name")上的"name"String paramName = ((RequestParam) ans).value();//如果@RequestParam("name")这里面if (!"".equals(paramName.trim())) {paramMap.put(paramName, i);}}}}HandlerModel model = new HandlerModel(method, entry.getValue(), paramMap);handlerMapping.put(realUrl, model);}}}
还有一个asm取方法名的工具类:
package com.tianyalei.mvc.util;import org.objectweb.asm.*;import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;/*** Created by wuwf on 17/6/30.*/
public class Play {/*** 获取指定类指定方法的参数名** @param method 要获取参数名的方法* @return 按参数顺序排列的参数名列表,如果没有参数,则返回null*/public static String[] getMethodParameterNamesByAsm4(final Class clazz, final Method method) {final String methodName = method.getName();final Class<?>[] methodParameterTypes = method.getParameterTypes();final int methodParameterCount = methodParameterTypes.length;String className = method.getDeclaringClass().getName();final boolean isStatic = Modifier.isStatic(method.getModifiers());final String[] methodParametersNames = new String[methodParameterCount];int lastDotIndex = className.lastIndexOf(".");className = className.substring(lastDotIndex + 1) + ".class";InputStream is = clazz.getResourceAsStream(className);try {ClassReader cr = new ClassReader(is);ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);cr.accept(new ClassAdapter(cw) {public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);final Type[] argTypes = Type.getArgumentTypes(desc);//参数类型不一致if (!methodName.equals(name) || !matchTypes(argTypes, methodParameterTypes)) {return mv;}return new MethodAdapter(mv) {public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {//如果是静态方法,第一个参数就是方法参数,非静态方法,则第一个参数是 this ,然后才是方法的参数int methodParameterIndex = isStatic ? index : index - 1;if (0 <= methodParameterIndex && methodParameterIndex < methodParameterCount) {methodParametersNames[methodParameterIndex] = name;}super.visitLocalVariable(name, desc, signature, start, end, index);}};}}, 0);} catch (Exception e) {e.printStackTrace();}return methodParametersNames;}/*** 比较参数是否一致*/private static boolean matchTypes(Type[] types, Class<?>[] parameterTypes) {if (types.length != parameterTypes.length) {return false;}for (int i = 0; i < types.length; i++) {if (!Type.getType(parameterTypes[i]).equals(types[i])) {return false;}}return true;}}



完成这一步后,重启看看映射的结果:


发现已经正确建立了映射关系。再下一步就可以根据doPost里取到的用户传来的参数找到对应的方法,并invoke方法了。
下面放一个DispatcherServlet的代码,下一篇我们就来完成整个请求处理链。
package com.tianyalei.mvc;import com.tianyalei.mvc.annotation.*;
import com.tianyalei.mvc.util.Play;import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** Created by wuwf on 17/6/28.* 入口Sevlet*/
public class DispatcherServlet extends HttpServlet {private List<String> classNames = new ArrayList<>();private Map<String, Object> instanceMapping = new HashMap<>();private Map<String, HandlerModel> handlerMapping = new HashMap<>();@Overridepublic void init(ServletConfig config) throws ServletException {System.out.println("我是初始化方法");scanPackage(config.getInitParameter("scanPackage"));doInstance();//注入值doAutoWired();doHandlerMapping();System.out.println(instanceMapping);}/*** 扫描包下的所有类*/private void scanPackage(String pkgName) {//获取指定的包的实际路径url,将com.tianyalei.mvc变成目录结构com/tianyalei/mvcURL url = getClass().getClassLoader().getResource("/" + pkgName.replaceAll("\\.", "/"));//转化成file对象File dir = new File(url.getFile());//递归查询所有的class文件for (File file : dir.listFiles()) {//如果是目录,就递归目录的下一层,如com.tianyalei.mvc.controllerif (file.isDirectory()) {scanPackage(pkgName + "." + file.getName());} else {//如果是class文件,并且是需要被spring托管的if (!file.getName().endsWith(".class")) {continue;}//举例,className = com.tianyalei.mvc.controller.WebControllerString className = pkgName + "." + file.getName().replace(".class", "");//判断是否被Controller或者Service注解了,如果没注解,那么我们就不管它,譬如annotation包和DispatcherServlet类我们就不处理try {Class<?> clazz = Class.forName(className);if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)) {classNames.add(className);}} catch (ClassNotFoundException e) {e.printStackTrace();}}}}/*** 实例化*/private void doInstance() {if (classNames.size() == 0) {return;}//遍历所有的被托管的类,并且实例化for (String className : classNames) {try {Class<?> clazz = Class.forName(className);//如果是Controllerif (clazz.isAnnotationPresent(Controller.class)) {//举例:webController -> new WebControllerinstanceMapping.put(lowerFirstChar(clazz.getSimpleName()), clazz.newInstance());} else if (clazz.isAnnotationPresent(Service.class)) {//获取注解上的值Service service = clazz.getAnnotation(Service.class);//举例:QueryServiceImpl上的@Service("myQueryService")String value = service.value();//如果有值,就以该值为keyif (!"".equals(value.trim())) {instanceMapping.put(value.trim(), clazz.newInstance());} else {//没值时就用接口的名字首字母小写//获取它的接口Class[] inters = clazz.getInterfaces();//此处简单处理了,假定ServiceImpl只实现了一个接口for (Class c : inters) {//举例 modifyService->new ModifyServiceImpl()instanceMapping.put(lowerFirstChar(c.getSimpleName()), clazz.newInstance());break;}}}} catch (Exception e) {e.printStackTrace();}}}/*** 给被AutoWired注解的属性注入值*/private void doAutoWired() {if (instanceMapping.isEmpty()) {return;}//遍历所有被托管的对象for (Map.Entry<String, Object> entry : instanceMapping.entrySet()) {//查找所有被Autowired注解的属性// getFields()获得某个类的所有的公共(public)的字段,包括父类;// getDeclaredFields()获得某个类的所有申明的字段,即包括public、private和proteced,但是不包括父类的申明字段。Field[] fields = entry.getValue().getClass().getDeclaredFields();for (Field field : fields) {//没加autowired的不需要注值if (!field.isAnnotationPresent(Autowired.class)) {continue;}String beanName;//获取AutoWired上面写的值,譬如@Autowired("abc")Autowired autowired = field.getAnnotation(Autowired.class);if ("".equals(autowired.value())) {//例 searchService。注意,此处是获取属性的类名的首字母小写,与属性名无关,可以定义@Autowired SearchService abc都可以。beanName = lowerFirstChar(field.getType().getSimpleName());} else {beanName = autowired.value();}//将私有化的属性设为true,不然访问不到field.setAccessible(true);//去映射中找是否存在该beanName对应的实例对象if (instanceMapping.get(beanName) != null) {try {field.set(entry.getValue(), instanceMapping.get(beanName));} catch (IllegalAccessException e) {e.printStackTrace();}}}}}/*** 建立url到方法的映射*/private void doHandlerMapping() {if (instanceMapping.isEmpty()) {return;}//遍历托管的对象,寻找Controllerfor (Map.Entry<String, Object> entry : instanceMapping.entrySet()) {Class<?> clazz = entry.getValue().getClass();//只处理Controller的,只有Controller有RequestMappingif (!clazz.isAnnotationPresent(Controller.class)) {continue;}//定义urlString url = "/";//取到Controller上的RequestMapping值if (clazz.isAnnotationPresent(RequestMapping.class)) {RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);url += requestMapping.value();}//获取方法上的RequestMappingMethod[] methods = clazz.getMethods();//只处理带RequestMapping的方法for (Method method : methods) {if (!method.isAnnotationPresent(RequestMapping.class)) {continue;}RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);//requestMapping.value()即是在requestMapping上注解的请求地址,不管用户写不写"/",我们都给他补上String realUrl = url + "/" + methodMapping.value();//替换掉多余的"/",因为有的用户在RequestMapping上写"/xxx/xx",有的不写,所以我们处理掉多余的"/"realUrl = realUrl.replaceAll("/+", "/");//获取所有的参数的注解,有几个参数就有几个annotation[],为毛是数组呢,因为一个参数可以有多个注解……Annotation[][] annotations = method.getParameterAnnotations();//由于后面的Method的invoke时,需要传入所有参数的值的数组,所以需要保存各参数的位置/*以Search方法的这几个参数为例 @RequestParam("name") String name, HttpServletRequest request, HttpServletResponse response未来在invoke时,需要传入类似这样的一个数组["abc", request, response]。"abc"即是在Post方法中通过request.getParameter("name")来获取Request和response这个简单,在post方法中直接就有。所以我们需要保存@RequestParam上的value值,和它的位置。譬如 name->0,只有拿到了这两个值,才能将post中通过request.getParameter("name")得到的值放在参数数组的第0个位置。同理,也需要保存request的位置1,response的位置2*/Map<String, Integer> paramMap = new HashMap<>();//获取方法里的所有参数的参数名(注意:此处使用了ASM.jar 版本为asm-3.3.1,需要在web-inf下建lib文件夹,引入asm-3.3.1.jar,自行下载)//如Controller的add方法,将得到如下数组["name", "addr", "request", "response"]String[] paramNames = Play.getMethodParameterNamesByAsm4(clazz, method);//获取所有参数的类型,提取Request和Response的索引Class<?>[] paramTypes = method.getParameterTypes();for (int i = 0; i < annotations.length; i++) {//获取每个参数上的所有注解Annotation[] anns = annotations[i];if (anns.length == 0) {//如果没有注解,则是如String abc,Request request这种,没写注解的//如果没被RequestParam注解// 如果是Request或者Response,就直接用类名作key;如果是普通属性,就用属性名Class<?> type = paramTypes[i];if (type == HttpServletRequest.class || type == HttpServletResponse.class) {paramMap.put(type.getName(), i);} else {//参数没写@RequestParam注解,只写了String name,那么通过java是无法获取到name这个属性名的//通过上面asm获取的paramNames来映射paramMap.put(paramNames[i], i);}continue;}//有注解,就遍历每个参数上的所有注解for (Annotation ans : anns) {//找到被RequestParam注解的参数,并取value值if (ans.annotationType() == RequestParam.class) {//也就是@RequestParam("name")上的"name"String paramName = ((RequestParam) ans).value();//如果@RequestParam("name")这里面if (!"".equals(paramName.trim())) {paramMap.put(paramName, i);}}}}HandlerModel model = new HandlerModel(method, entry.getValue(), paramMap);handlerMapping.put(realUrl, model);}}}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {out(resp, "请求到我啦");
//        doInvoke(req, resp);}//    private void doInvoke(HttpServletRequest req, HttpServletResponse resp) {
//        String url = req.getRequestURI();
//        try {
//            if (handlerMapping.get(url) == null) {
//                resp.getWriter().write("404 not found");
//                return;
//            }
//            Method method = handlerMapping.get(url);
//            method.invoke(null,null);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }private void out(HttpServletResponse response, String str) {try {response.setContentType("application/json;charset=utf-8");response.getWriter().print(str);} catch (IOException e) {e.printStackTrace();}}private class HandlerModel {Method method;Object controller;Map<String, Integer> paramMap;public HandlerModel(Method method, Object controller, Map<String, Integer> paramMap) {this.method = method;this.controller = controller;this.paramMap = paramMap;}}private String lowerFirstChar(String className) {char[] chars = className.toCharArray();chars[0] += 32;return String.valueOf(chars);}}






这篇关于3 手写实现SpringMVC,第三节:通过反射给属性和参数注入值的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/879946

相关文章

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

一文详解SpringBoot中控制器的动态注册与卸载

《一文详解SpringBoot中控制器的动态注册与卸载》在项目开发中,通过动态注册和卸载控制器功能,可以根据业务场景和项目需要实现功能的动态增加、删除,提高系统的灵活性和可扩展性,下面我们就来看看Sp... 目录项目结构1. 创建 Spring Boot 启动类2. 创建一个测试控制器3. 创建动态控制器注

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Java操作Word文档的全面指南

《Java操作Word文档的全面指南》在Java开发中,操作Word文档是常见的业务需求,广泛应用于合同生成、报表输出、通知发布、法律文书生成、病历模板填写等场景,本文将全面介绍Java操作Word文... 目录简介段落页头与页脚页码表格图片批注文本框目录图表简介Word编程最重要的类是org.apach

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

在Linux中改变echo输出颜色的实现方法

《在Linux中改变echo输出颜色的实现方法》在Linux系统的命令行环境下,为了使输出信息更加清晰、突出,便于用户快速识别和区分不同类型的信息,常常需要改变echo命令的输出颜色,所以本文给大家介... 目python录在linux中改变echo输出颜色的方法技术背景实现步骤使用ANSI转义码使用tpu

Spring Boot中WebSocket常用使用方法详解

《SpringBoot中WebSocket常用使用方法详解》本文从WebSocket的基础概念出发,详细介绍了SpringBoot集成WebSocket的步骤,并重点讲解了常用的使用方法,包括简单消... 目录一、WebSocket基础概念1.1 什么是WebSocket1.2 WebSocket与HTTP

SpringBoot+Docker+Graylog 如何让错误自动报警

《SpringBoot+Docker+Graylog如何让错误自动报警》SpringBoot默认使用SLF4J与Logback,支持多日志级别和配置方式,可输出到控制台、文件及远程服务器,集成ELK... 目录01 Spring Boot 默认日志框架解析02 Spring Boot 日志级别详解03 Sp

Python使用python-can实现合并BLF文件

《Python使用python-can实现合并BLF文件》python-can库是Python生态中专注于CAN总线通信与数据处理的强大工具,本文将使用python-can为BLF文件合并提供高效灵活... 目录一、python-can 库:CAN 数据处理的利器二、BLF 文件合并核心代码解析1. 基础合

java中反射Reflection的4个作用详解

《java中反射Reflection的4个作用详解》反射Reflection是Java等编程语言中的一个重要特性,它允许程序在运行时进行自我检查和对内部成员(如字段、方法、类等)的操作,本文将详细介绍... 目录作用1、在运行时判断任意一个对象所属的类作用2、在运行时构造任意一个类的对象作用3、在运行时判断