Mybatis源码解析(七):Mapper代理原理

news2024/12/28 21:36:55

Mybatis源码系列文章

手写源码(了解源码整体流程及重要组件)

Mybatis源码解析(一):环境搭建

Mybatis源码解析(二):全局配置文件的解析

Mybatis源码解析(三):映射配置文件的解析

Mybatis源码解析(四):sql语句及#{}、${}的解析

Mybatis源码解析(五):SqlSession会话的创建

Mybatis源码解析(六):缓存执行器操作流程

Mybatis源码解析(六):查询数据库主流程

Mybatis源码解析(七):Mapper代理原理


目录

  • 前言
  • 一、环境准备
  • 二、引入映射配置文件方式
  • 三、\<package name="com.xxx.mapper"/>标签的解析
    • 1、通过包路径获取Mapper接口
    • 2、注解方式mapper接口的解析
    • 3、xml和mapper接口需要同包同名的原因?
  • 四、Mapper接口代理对象的生成
  • 五、代理对象执行接口方法的流程
  • 总结


前言

  • 文章主要围绕着如下几个点,展开源码解析:
    • <package name=“com.xxx.mapper”/>;是如何进行解析的?
    • sqlSession.getMapper(UserMapper.class);是如何生成的代理对象?
    • mapperProxy.findById(1);是怎么完成的增删改查操作?

一、环境准备

  • java代码
@Test
public void test2() throws IOException {

  // 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中 注意:配置文件并没有被解析
  InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

  // 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象
  SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

  // 3. (1)创建事务对象 (2)创建了执行器对象cachingExecutor (3)创建了DefaultSqlSession对象
  SqlSession sqlSession = sqlSessionFactory.openSession();

  // 4. JDK动态代理生成代理对象
  UserMapper mapperProxy = sqlSession.getMapper(UserMapper.class);

  // 5.代理对象调用方法
  User user = mapperProxy.findUserById(100);

  System.out.println("MyBatis源码环境搭建成功....");

  sqlSession.close();
}
  • 核心配置文件sqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--第一部分:数据源配置-->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理 -->
            <transactionManager type="JDBC"/>
            <!-- 数据库连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456789"/>
            </dataSource>
        </environment>
    </environments>

    <!--第二部分:引入映射配置文件-->
    <mappers>
        <!--使用相对路径注册映射文件-->
        <!--    <mapper resource="mapper/UserMapper.xml"/>-->
        <!--使用绝对路径注册映射文件-->
        <!-- <mapper url="file:///D:\javaCode\mybatis-3.5.7\src\test\resources\mapper\UserMapper.xml"/>-->
        <!--注册持久层接口-->
        <!-- <mapper class="com.xc.mapper.UserMapper"/>-->
        <!--注册一个包下的所有持久层接口-->
        <package name="com.xc.mapper"/>
    </mappers>
</configuration>
  • 实体映射配置文件UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xc.mapper.UserMapper">
    <select id="findUserById" parameterType="int" resultType="com.xc.pojo.User"  >
        SELECT id,username FROM  user WHERE id = #{id}
    </select>
</mapper>

二、引入映射配置文件方式

先说个结论,后续源码验证:如果不指定xml,则会在Mapper接口同目录下寻找

  • 方式一:<mapper resource=“mapper/UserMapper.xml”/> 指定xml,缺点需要每个映射xml都要手动添加
  • 方式二:<mapper class=“com.xc.mapper.UserMapper”/> 没有指定xml,会从同目录下寻找xml,缺点也是需要每个Mapper接口都要手动添加
  • 方式三:<package name=“com.xc.mapper”/> 没有指定xml,会从同目录下寻找xml,会遍历此包下所有Mappe接口

三、<package name=“com.xxx.mapper”/>标签的解析

  • 为什么单独讲这个标签?
    1. 这个标签是多种引入映射文件的最佳选,也是工作中必用的
    2. 创建代理类工厂,为以后通过Mapper接口类生成代理实现类做准备
  • <package>标签在核心配置文件的<mappers>标签下
  • 方式一也会创建代理类工厂,不过是在解析xml文件后,方式三是先创建代理类工厂,再解析xml
  • Mybatis源码解析(三):映射配置文件的解析:这篇单独讲了<mapper resource=“mapper/UserMapper.xml”/>指定配置文件的解析

进入解析<mappers>标签方法

  • <mapper>子标签的解析在Mybatis源码解析(三):映射配置文件的解析里详细讲了,这里讲下<package>子标签的解析
  • 获取包名,调用addMappers方法
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 获取<mappers>标签的子标签
    for (XNode child : parent.getChildren()) {
      // <package>子标签
      if ("package".equals(child.getName())) {
        // 获取mapper接口和mapper映射文件对应的package包名
        String mapperPackage = child.getStringAttribute("name");
        // 将包下所有的mapper接口以及它的代理工厂对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
        configuration.addMappers(mapperPackage);
      } else {// <mapper>子标签
        // 获取<mapper>子标签的resource属性
        String resource = child.getStringAttribute("resource");
        // 获取<mapper>子标签的url属性
        String url = child.getStringAttribute("url");
        // 获取<mapper>子标签的class属性
        String mapperClass = child.getStringAttribute("class");
        // 它们是互斥的
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
            // 专门用来解析mapper映射文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 通过XMLMapperBuilder解析mapper映射文件
            mapperParser.parse();
          }
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          try(InputStream inputStream = Resources.getUrlAsStream(url)){
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            // 通过XMLMapperBuilder解析mapper映射文件
            mapperParser.parse();
          }
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          // 将指定mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

进入configuration的addMappers方法

public void addMappers(String packageName) {
  mapperRegistry.addMappers(packageName);
}
  • mapperRegistry对象中核心属性就是knownMappers
    • key:Mapper接口的Class对象
    • value:Mapper接口代理类工厂
public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  ...
}

进入mapperRegistry的addMappers方法

public void addMappers(String packageName) {
  addMappers(packageName, Object.class);
}

1、通过包路径获取Mapper接口

  • resolverUtil.find方法:加载包路径下Mapper接口
  • mapperSet:mapper接口Class对象集合
  • addMapper方法:将Mapper接口添加到上面所说的Map集合knownMappers中
public void addMappers(String packageName, Class<?> superType) {
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  // 根据package名称,加载该包下Mapper接口文件(不是映射文件)
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  // 获取加载的Mapper接口
  Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  for (Class<?> mapperClass : mapperSet) {
    // 将Mapper接口添加到MapperRegistry中
    addMapper(mapperClass);
  }
}

resolverUtil.find方法

  • getPackagePath方法:将包名-com.xc.mapper转换为资源路径-com/xc/mapper(.替换成/)
  • children:获取资源路径下的资源,如下
    在这里插入图片描述
  • addIfMatching方法:将Mapper接口的Class对象添加到matches集合中
public ResolverUtil<T> find(Test test, String packageName) {
  String path = getPackagePath(packageName);

  try {
    List<String> children = VFS.getInstance().list(path);
    for (String child : children) {
      if (child.endsWith(".class")) {
        addIfMatching(test, child);
      }
    }
  } catch (IOException ioe) {
    log.error("Could not read package: " + packageName, ioe);
  }

  return this;
}

resolverUtil.getClasses()

private Set<Class<? extends T>> matches = new HashSet<>();
...  
public Set<Class<? extends T>> getClasses() {
  return matches;
}

addMapper方法

  • 循环遍历matches集合,将所有Mapper接口Class对象添加到knownMappers
    • key:Mapper接口的Class对象
    • value:Mapper接口代理类工厂
  • 创建注解解析Builder,调用parse解析方法(xml的解析也包含在内)
public <T> void addMapper(Class<T> type) {
  if (type.isInterface()) {
    // 如果Map集合中已经有该mapper接口的映射,就不需要再存储了
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      // 将mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
      knownMappers.put(type, new MapperProxyFactory<>(type));
      // 用来解析注解方式的mapper接口
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      // 解析注解方式的mapper接口
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

2、注解方式mapper接口的解析

  • loadXmlResource方法:xml文件的解析
  • parseStatement方法:原理其实和Mybatis源码解析(三):映射配置文件的解析差不多
    • 不同点:xml解析<select>标签内的属性,注解解析@select注解里的属性
    • 相同点:最终目的都是解析成MappedStatement对象
public void parse() {
  // 获取mapper接口的全路径
  String resource = type.toString();
  // 是否解析过该mapper接口
  if (!configuration.isResourceLoaded(resource)) {
    // 先解析mapper映射文件
    loadXmlResource();
    // 设置解析标识
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    // 解析CacheNamespace注解
    parseCache();
    // 解析CacheNamespaceRef注解
    parseCacheRef();
    for (Method method : type.getMethods()) {
      if (!canHaveStatement(method)) {
        continue;
      }
      if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
          && method.getAnnotation(ResultMap.class) == null) {
        parseResultMap(method);
      }
      try {
        // 每个mapper接口中的方法,都解析成MappedStatement对象
        parseStatement(method);
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  parsePendingMethods();
}

3、xml和mapper接口需要同包同名的原因?

进入上步骤的xml解析方法loadXmlResource方法

  • type:Mapper接口的Class对象
  • 通过Mapper接口名字(com.xc.UserMapper),.替换/转换成资源路径,再添加后缀.xml获取mapper对应的xml
  • 通过资源路径加载为输入流
  • 然后创建xml解析Builder对象,再调用解析方法,就是Mybatis源码解析(三):映射配置文件的解析的内容了
private void loadXmlResource() {
  if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
    String xmlResource = type.getName().replace('.', '/') + ".xml";
    // #1347
    InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
    if (inputStream == null) {
      // Search XML mapper that is not in the module but in the classpath.
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e2) {
        // ignore, resource is not required
      }
    }
    if (inputStream != null) {
      XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
      xmlParser.parse();
    }
  }
}

四、Mapper接口代理对象的生成

sqlSession.getMapper(UserMapper.class)通过Mapper接口Class对象生成代理对象

  • 其实就是通过Mapper接口Class对象,获取上面说的接口代理类工厂
  • 代理类工厂调用.newInstance创建接口代理类
@Override
public <T> T getMapper(Class<T> type) {
  // 从Configuration对象中,根据Mapper接口,获取Mapper代理对象
  return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  // 根据Mapper接口的类型,从Map集合中获取Mapper代理对象工厂
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    // 通过MapperProxyFactory生产MapperProxy,通过MapperProxy产生Mapper代理对象
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

进入newInstance方法

  • Proxy.newProxyInstance:jdk动态代理,Mapper接口的代理类,从这里创建
  • 代理方法第三个参数是InvocationHandler的实现类,invoke方法就是代理类实现接口类方法的内容
public T newInstance(SqlSession sqlSession) {
  // InvocationHandler接口的实现类
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
  // 使用JDK动态代理方式,生成代理对象
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

五、代理对象执行接口方法的流程

根据jdk动态代理可知,调用接口方法则会进入invoke方法,里面会有接口方法的实现内容

  • 如果是Object定义方法,则MapperProxy类直接调用方法
  • mapperProxy.findUserById(100):代理类调用接口方法,debug则会进入invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 如果是 Object 定义的方法,直接调用
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else {
      // 代理逻辑在这
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

进入cachedInvoker(method).invoke方法(代理逻辑)

  • command.getType():增删改查类型标记
  • command.getName():statementId(namespace:id)
  • sqlSession.selectOne(command.getName(), param):Mybatis源码解析(六):查询数据库主流程
  • sqlSession.insert、sqlSession.update、sqlSession.delete调用方法相同,都是executor.update,所有xml中<insert><update><delete>三个标签的作用一样,只是为了看上去区分一下
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
  return mapperMethod.execute(sqlSession, args);
}
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  // 判断mapper中的方法类型
  switch (command.getType()) {
    // 添加
    case INSERT: {
      // 转换参数
      Object param = method.convertArgsToSqlCommandParam(args);
      // 最终调用的还是sqlSession中的方法
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    // 更新
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    // 删除
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    // 查询
    case SELECT:
       // 无返回结果,并且有ResultHandler方法参数,将查询结果交给ResultHandler进行处理
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
        // 执行查询、返回列表
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
        // 执行查询、返回Map
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
        // 执行查询、返回Cursor
      } else if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        // 转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        // 查询单条
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("Mapper method '" + command.getName()
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result;
}

补充说明下SqlCommand: command对象

  • 通过接口方法名获取对应xml的MappedStatement对象(每一个赠送改查标签对应一个),这里也说明了为啥接口名要与<insert><update><delete><select>标签内的id一致,就是通过方法名匹配标签id获取MappedStatement
  • command.getType():是<insert><update><delete><select>标签解析出增删改查类型
  • command.getName():MappedStatement的id,statementId
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  // 当前调用的方法名称
  final String methodName = method.getName();
  // 当前执行的方法对应的Class
  final Class<?> declaringClass = method.getDeclaringClass();
  // 获取对应的MappedStatement
  MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
      configuration);
  if (ms == null) {
    if (method.getAnnotation(Flush.class) != null) {
      name = null;
      type = SqlCommandType.FLUSH;
    } else {
      throw new BindingException("Invalid bound statement (not found): "
          + mapperInterface.getName() + "." + methodName);
    }
  } else {
    name = ms.getId();
    type = ms.getSqlCommandType();
    if (type == SqlCommandType.UNKNOWN) {
      throw new BindingException("Unknown execution method for: " + name);
    }
  }
}

总结

  • <package>标签配置的包名下的Mapper接口文件都会被加载成对应的代理类工厂
  • 通过Mapper接口获取同包同名的xml文件,并解析
  • Mapper接口通过jdk代理创建代理类,接口方法匹配xml中标签的id值,执行增删改查

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用R语言对S&P500股票指数进行ARIMA + GARCH交易策略

在本文中&#xff0c;我想向您展示如何应用S&#xff06;P500股票市场指数的交易策略。最近我们被客户要求撰写关于交易策略的研究报告&#xff0c;包括一些图形和统计输出。 通过组合ARIMA GARCH模型&#xff0c;从长期来看&#xff0c;我们可以超过“买入并持有”方法。 相…

【MySQL基础】常用指令详解

如果看不清未来&#xff0c;就走好当下的路&#xff0c;做你此刻该去做的事。——《冰雪奇缘2》 目录 1、进入和退出mysql 1.1进入mysql 1.2退出mysql 2、查看mysql中有哪些数据库 2.2.创建数据库 3、使用数据库 3.1开始使用数据库 3.2展示数据库中的表 4、查看表中的…

跨境电商面临“寒冬”考验,如何转型升级入局新赛道(Starday)

近几年随着互联网和高新技术的飞速发展&#xff0c;加之疫情下各国海外贸易政策的管理&#xff0c;跨境贸易模式不断地创新升级&#xff0c;现今的跨境贸易模式已经从线下交易上升为线上交易&#xff0c;各种基于互联网商务网站的电子商务业务和网络公司开始不断地涌现&#xf…

WebDAV之葫芦儿•派盘+FolderSync

FolderSync 支持WebDAV方式连接葫芦儿派盘。 随着业务发展,文件数据增长,如文档更新、资料下载、拍照录像等。如何更好的管理这些资料,不出现丢失的问题就成为了一个很大的问题。也正是有了类似的需求,现在网络上出现了很多的文件同步备份软件。那么,文件同步备份软件哪…

Thread类的start()方法创建线程的底层分析

在Java中通过如下简单代码就可以创建一个新线程 Thread thread new Thread(new Runnable() {Overridepublic void run() {//do something} }); thread.start(); 在start()中又调用了start0()&#xff0c;它才是真正创建线程的方法。 public synchronized void start() {gro…

安全机制(security) - 加解密算法 - 对称加密 - 加解密模式

说明 大部分对称加密算法支持多种加密模式&#xff0c;每种模式的运算结果也不相同。加解密模式是分组加密算法通用的机制&#xff0c;不同算法可能支持相同的加密模式&#xff0c;不同算法支持的加密模式也可能不同。加密和解密需要使用相同的模式才能得到正确的结果。不同的…

CANOE功能介绍

1.CANoe主界面 当计算机安装完CANoe后&#xff0c;用户只需选择“开始”→“所有程序 ”→Vector CANoe 11.0→CANoe 11.0 系 统 菜 单 命 令 即 可 启 动CANoe。 为了快速熟悉CANoe的常用功能&#xff0c;我们可以打开Vector官方的自带例程&#xff0c;一边学习一边实践相关功…

超算/先进计算如何改变现如今对的生活

算力作为新一代的“石油”&#xff0c;与超算/先进计算有着不可分割的紧密联系。 通俗而言&#xff0c;算力泛指计算能力&#xff0c;即数据处理能力。算力大小代表数据处理能力的强弱。从远古的结绳计算到近代的机械式计算&#xff0c;再到现代的数字电子计算&#xff0c;特别…

Ajax学习:设置CROS响应头实现跨域(跨域资源共享)

CROS:跨域资源共享、是官方的跨域解决方案&#xff0c;特点不需要在客户端做任何特殊的操作&#xff0c;完全在服务器中处理&#xff08;支持get post 等&#xff09; 客户端做ajax请求&#xff0c;服务端做相应头设置就可以实现跨域&#xff1a; <!DOCTYPE html> <h…

如何快速构建研发效能度量的指标体系?

本月初&#xff0c;没毛病软件公司的研发总监 Kevin 在参加完公司管理层月度例会后&#xff0c;心情非常糟糕...... 刚才会议中&#xff0c;老板很严肃地问研发总监 Kevin&#xff1a;“我在会议前接到了客户的投诉电话&#xff0c;说产品出现了 Bug&#xff0c;这已经不是第一…

.net-----Windows 窗体应用程序包括控件,对话框,多重窗体,绘制图形,菜单和工具栏

目录前言Windows窗体应用程序概述&#xff1b;窗体和大部分控件常用的事件创建Windows窗体应用程序使用Visual Studio集成开发环境实现Hello World程序使用常用Windows窗体控件&#xff1b;Label、TextBox、RichTextBox、Button应用示例单选按钮、复选框和分组【例】RadioButto…

(附源码)springboot物流配货管理系统 毕业设计 250858

基于springboot物流配货管理系统的设计与实现 摘 要 信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题.针对物流配货等问题,对物流配货进行研究分析,然后…

电力系统机组组合优化调度(IEEE14节点、IEEE30节点、IEEE118节点)(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f4dd;目前更新&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;电力系统相关知识&#xff0c;期刊论文&…

数云融合丨知识图谱在烟草零售数字化转型中的应用

一、知识图谱的趋势 随着互联网、云计算、大数据、人工智能等信息数据技术的快速发展&#xff0c;计算机的智能化程度也越来越高&#xff0c;知识图谱作为人工智能的核心技术&#xff0c;其在数据集成、语义表示和逻辑推理等方面存在着得天独厚的优势。 2021年&#xf…

Java并发-交替打印的四种方法。

1 前言 如下图所示&#xff0c;现在有两个线程A,B&#xff1b;A打印12345&#xff0c;B打印abcde&#xff0c;结果为1a2b3c4d5e交替输出。 1.1 采用wait和notify 【分析】我们要求线程A始终先打印&#xff0c;因此在线程B先获得CPU使用时间时也应该阻塞。 细节 线程A应该打印…

【人工智能/算法】搜索求解(Solving Problemsby Searching)

文章目录一、求解与搜索二、盲目式搜索1. 深度优先搜索&#xff08;Depth First Search, DFS&#xff09;回溯搜索&#xff08;Backtracking Search&#xff09;2. 广度优先搜索&#xff08;Breadth First Search, BFS&#xff09;一致代价搜索&#xff08;Uniform-cost Search…

TLog轻量级分布式日志标记追踪神器

文章目录TLog简介项目特性安装TLogspringboot依赖spring native依赖日志框架适配方式(举例Log4j框架适配器)任务框架支持(举例XXL-JOB框架)TLog架构图TLog简介 TLog通过对日志打标签完成企业级微服务的日志追踪。它不收集日志&#xff0c;使用简单&#xff0c; 产生全局唯一的…

Actor 生命周期

一&#xff0c;一览图 二&#xff0c; 大致流程 三&#xff0c;细节 从磁盘加载 已位于关卡中的 Actor 使用此路径&#xff0c;如 LoadMap 发生时、或 AddToWorld&#xff08;从流关卡或子关卡&#xff09;被调用时。 包/关卡中的 Actor 从磁盘中进行加载。 PostLoad - 在序…

支持向量机(SVM)—— 详细推导及案例应用可视化

支持向量机&#xff08;SVM&#xff09; 1. 什么是支持向量机&#xff1f; 在上图中&#xff0c;我们想在二维平面中通过画出一条分界线将黑点与白点分开&#xff0c;很明显&#xff0c;我们认为在上面所画的三条分界线中H3H_3H3​是最好的&#xff0c;因为H1H_1H1​压根就没有…

【行为识别】差影法三维人体姿态行为识别【含Matlab源码 277期】

⛄一、简介 该课题为基于MATLAB差影法的人体姿态识别。需要准备对应的模板图片作为背景图&#xff0c;然后测试图和背景图进行作差&#xff0c;结合形态学知识&#xff0c;提取出人体轮廓&#xff0c;接上最外接矩形&#xff0c;得出矩形长宽&#xff0c;计算长宽比例&#xf…