Mybatis源码分析(三)SqlSessionFactoryl的初始化

news2024/11/22 6:15:28

目录

  • 一 解析配置文件入口
  • 二 解析properties文件
  • 三 解析settings文件
  • 四 解析typeAliases文件
  • 五 解析 Plugin文件
  • 六 解析 Environments 文件
  • 七 解析Mapper 文件

官网: mybatis – MyBatis 3 | 简介
参考书籍:《通用源码阅读指导书:MyBatis源码详解》 易哥
参考文章:

  • Mybatis源码解析

上篇文件我们介绍了如何获取到Mybatis-conf.xml文件
的过程,现在我们来看看获取获取SqlSessionFactory

一 解析配置文件入口

    	 // 第一阶段:MyBatis的初始化阶段
        String resource = "mybatis-config.xml";
        // 得到配置文件的输入流
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 得到SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  • 我们看到代码中Build方法,就是程序的入口,我们来看一看吧
// 第一步调用这个方法
public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
// 第二部调用重载方法
 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        // 创建文件解析器
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
        // 调用解析器的parse方法
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }


  • **看到这个结构,我们应该感到熟悉,啊,这不就是建造者模式吗?没错,SqlSessionFactory中用了设计模式中的建造者模式 **

image.png

  • 我们可以看到重载方法中调用了XMLConfigBuilder()方法,来解析文件,所以我们来瞅瞅
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        // 调用父类的方法
      super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
 

image.png

  • 可以看到XMLConfigBuilder继承BaseBuilder,我们先来看看这个方法吧,这个方法有多个实现类,每一个子类多是以XMLXXXXXBuilder命名的,也就是其子类都对应解析一种xml文件或xml文件中一种元素。

image.png
BaseBuilder中只有三个成员变量,而typeAliasRegistry和typeHandlerRegistry都是直接从Configuration的成员变量获得的,接着我们看看Configuration这个类
Configuration类位于mybatis包的org.apache.ibatis.session目录下,其属性就是对应于mybatis的全局配置文件mybatis-config.xml的配置,将XML配置中的内容解析赋值到Configuration对象中。

public abstract class BaseBuilder {
    // 配置文件,对应其属性就是对应于mybatis的全局配置文件mybatis-config.xml的配置,将XML配置中的内容解析赋值到Configuration对象中。
  protected final Configuration configuration;
    // 类型注册
  protected final TypeAliasRegistry typeAliasRegistry;
    // 类型处理器
  protected final TypeHandlerRegistry typeHandlerRegistry;

  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
}
  • 由于XML配置项有很多,所以Configuration类的属性也很多。先来看下Configuration对于的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>    
     
     <!-- 属性配置,读取properties中的配置文件 -->
    <properties resource="db.propertis">
       <property name="XXX" value="XXX"/>
    </properties>
    
    <!-- 类型别名 -->
    <typeAliases>
       <!-- 在用到User类型的时候,可以直接使用别名,不需要输入User类的全部路径 -->
       <typeAlias type="com.luck.codehelp.entity.User" alias="user"/>
    </typeAliases>

    <!-- 类型处理器 -->
    <typeHandlers>
        <!-- 类型处理器的作用是完成JDBC类型和java类型的转换,mybatis默认已经由了很多类型处理器,正常无需自定义-->
    </typeHandlers>
    
    <!-- 对象工厂 -->
    <!-- mybatis创建结果对象的新实例时,会通过对象工厂来完成,mybatis有默认的对象工厂,正常无需配置 -->
    <objectFactory type=""></objectFactory>
    
    <!-- 插件 -->
    <plugins>
        <!-- 可以自定义拦截器通过plugin标签加入 -->
       <plugin interceptor="com.lucky.interceptor.MyPlugin"></plugin>
    </plugins>
    
    <!-- 全局配置参数 -->
    <settings>   
        <setting name="cacheEnabled" value="false" />   
        <setting name="useGeneratedKeys" value="true" /><!-- 是否自动生成主键 -->
        <setting name="defaultExecutorType" value="REUSE" />   
        <setting name="lazyLoadingEnabled" value="true"/><!-- 延迟加载标识 -->
        <setting name="aggressiveLazyLoading" value="true"/><!--有延迟加载属性的对象是否延迟加载 -->
        <setting name="multipleResultSetsEnabled" value="true"/><!-- 是否允许单个语句返回多个结果集 -->
        <setting name="useColumnLabel" value="true"/><!-- 使用列标签而不是列名 -->
        <setting name="autoMappingBehavior" value="PARTIAL"/><!-- 指定mybatis如何自动映射列到字段属性;NONE:自动映射;PARTIAL:只会映射结果没有嵌套的结果;FULL:可以映射任何复杂的结果 -->
        <setting name="defaultExecutorType" value="SIMPLE"/><!-- 默认执行器类型 -->
        <setting name="defaultFetchSize" value=""/>
        <setting name="defaultStatementTimeout" value="5"/><!-- 驱动等待数据库相应的超时时间 ,单位是秒-->
        <setting name="safeRowBoundsEnabled" value="false"/><!-- 是否允许使用嵌套语句RowBounds -->
        <setting name="safeResultHandlerEnabled" value="true"/>
        <setting name="mapUnderscoreToCamelCase" value="false"/><!-- 下划线列名是否自动映射到驼峰属性:如user_id映射到userId -->
        <setting name="localCacheScope" value="SESSION"/><!-- 本地缓存(session是会话级别) -->
        <setting name="jdbcTypeForNull" value="OTHER"/><!-- 数据为空值时,没有特定的JDBC类型的参数的JDBC类型 -->
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/><!-- 指定触发延迟加载的对象的方法 -->
        <setting name="callSettersOnNulls" value="false"/><!--如果setter方法或map的put方法,如果检索到的值为null时,数据是否有用  -->
        <setting name="logPrefix" value="XXXX"/><!-- mybatis日志文件前缀字符串 -->
        <setting name="logImpl" value="SLF4J"/><!-- mybatis日志的实现类 -->
        <setting name="proxyFactory" value="CGLIB"/><!-- mybatis代理工具 -->
    </settings>  

    <!-- 环境配置集合 -->
    <environments default="development">    
        <environment id="development">    
            <transactionManager type="JDBC"/><!-- 事务管理器 -->
            <dataSource type="POOLED"><!-- 数据库连接池 -->    
                <property name="driver" value="com.mysql.jdbc.Driver" />    
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" />    
                <property name="username" value="root" />    
                <property name="password" value="root" />    
            </dataSource>    
        </environment>    
    </environments>    
    
    <!-- mapper文件映射配置 -->
    <mappers>    
        <mapper resource="com/luck/codehelp/mapper/UserMapper.xml"/>    
    </mappers>    
</configuration>
  • 我们来看看Configuration这个类的成员变量

// <environment>节点的信息
  protected Environment environment;

  // 以下为<settings>节点中的配置信息
  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;

  protected String logPrefix;
  protected Class<? extends Log> logImpl;
  protected Class<? extends VFS> vfsImpl;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ResultSetType defaultResultSetType;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
  // 以上为<settings>节点中的配置信息

  // <properties>节点信息
  protected Properties variables = new Properties();
  // 反射工厂
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  // 对象工厂
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  // 对象包装工厂
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  // 是否启用懒加载,该配置来自<settings>节点
  protected boolean lazyLoadingEnabled = false;
  // 代理工厂
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
  // 数据库编号
  protected String databaseId;
  // 配置工厂,用来创建用于加载反序列化的未读属性的配置。
  protected Class<?> configurationFactory;
  // 映射注册表
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  // 拦截器链(用来支持插件的插入)
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  // 类型处理器注册表,内置许多,可以通过<typeHandlers>节点补充
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  // 类型别名注册表,内置许多,可以通过<typeAliases>节点补充
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  // 语言驱动注册表
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  // 映射的数据库操作语句
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  // 缓存
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  // 结果映射,即所有的<resultMap>节点
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  // 参数映射,即所有的<parameterMap>节点
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  // 主键生成器映射
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
  // 载入的资源,例如映射文件资源
  protected final Set<String> loadedResources = new HashSet<>();
  // SQL语句片段,即所有的<sql>节点
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  // 暂存未处理完成的一些节点
  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

  // 用来存储跨namespace的缓存共享设置
  protected final Map<String, String> cacheRefMap = new HashMap<>();
  • 我们可以通过代码看到代码调用了的Configuration无参构造代码, typeAliasRegistry 注册了很多别名,我们看看TypeAliasRegistry
public Configuration() {
    
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
  
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }
  • 我们来看看别名注册表,我们可以发现其中已经有注册的文件,好了完成了这些,我们就可以得到一个 Configuration 对象,并且把我们Mybatis-conf.xml文件解析到其中
// 存储中心其实TypeAliasRegistry里面有一个HashMap,并且在TypeAliasRegistry的构造器中注册很多别名到这个hashMap中。
private final Map<String, Class<?>> typeAliases = new HashMap<>();

public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }
  • 开始调用parser.parse()方法,进行文件的解析
  public Configuration parse() {
    // 是否加载过
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 解析配置文件  configuration为开始节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  • 注意一个 xpath 表达式 - /configuration。这个表达式代表的是 MyBatis 的标签,这里选中这个标签,并传递给parseConfiguration方法。
private void parseConfiguration(XNode root) {
    try {
        // 解析 properties 配置
        propertiesElement(root.evalNode("properties"));

        // 解析 settings 配置,并将其转换为 Properties 对象
        Properties settings = settingsAsProperties(root.evalNode("settings"));

        // 加载 vfs
        loadCustomVfs(settings);

        // 解析 typeAliases 配置
        typeAliasesElement(root.evalNode("typeAliases"));

        // 解析 plugins 配置
        pluginElement(root.evalNode("plugins"));

        // 解析 objectFactory 配置
        objectFactoryElement(root.evalNode("objectFactory"));

        // 解析 objectWrapperFactory 配置
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

        // 解析 reflectorFactory 配置
        reflectorFactoryElement(root.evalNode("reflectorFactory"));

        // settings 中的信息设置到 Configuration 对象中
        settingsElement(settings);

        // 解析 environments 配置
        environmentsElement(root.evalNode("environments"));

        // 解析 databaseIdProvider,获取并设置 databaseId 到 Configuration 对象
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));

        // 解析 typeHandlers 配置
        typeHandlerElement(root.evalNode("typeHandlers"));

        // 解析 mappers 配置
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

二 解析properties文件

<properties resource="db.properties" >
  <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
  <property name="username" value="root"/>
  <property name="password" value="123456"/>
</properties>
// 解析properties文件
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        // 解析成Properties文件
        Properties defaults = context.getChildrenAsProperties();
        // 解析resource
        String resource = context.getStringAttribute("resource");
        // 解析url
        String url = context.getStringAttribute("url");
        
        if (resource != null && url != null) {
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
        }
        // // 从文件系统中加载并解析属性文件
        if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            // 通过 url 加载并解析属性文件
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        parser.setVariables(defaults);
          // 将属性值设置到 configuration 中
        configuration.setVariables(defaults);
    }
}
  • root.evalNode()方法,解析指定标签文件
 /**
   * 进行XML节点的解析
   * @param expression 解析的语句
   * @param root 解析根
   * @param returnType 返回值类型
   * @return 解析结果
   */
  private Object evaluate(String expression, Object root, QName returnType) {
    try {
      // 对指定节点root运行解析语法expression,获得returnType类型的解析结果
      return xpath.evaluate(expression, root, returnType);
    } catch (Exception e) {
      throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
    }
  }

image.png
image.png
image.png
image.png
image.png

  1. 解析 properties 节点的子节点,并将解析结果设置到 Properties 对象中。
  2. 从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。
  3. 将解析出的属性对象设置到 XPathParser 和 Configuration 对象中。

需要注意的是,propertiesElement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。

三 解析settings文件

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
</settings>
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
  • 我们可以看到调用settingsAsProperties的方法,看他是如何解析settings文件的
private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
	// 解析settings文件
    Properties props = context.getChildrenAsProperties();

    // 创建 Configuration 类的“元信息”对象
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }
  • **getChildrenAsProperties()方法我们在上面也见到过,这里就不多讲,我们来看看settingsElement(settings),为configuration配置我们的设置 **
private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

四 解析typeAliases文件

在 MyBatis 中,可以为我们自己写的有些类定义一个别名。这样在使用的时候,我们只需要输入别名即可,无需再把全限定的类名写出来。在 MyBatis 中,我们有两种方式进行别名配置。

  • 第一种是仅配置包名,让 MyBatis 去扫描包中的类型,并根据类型得到相应的别名
<typeAliases>     
    <package name="com.mybatis.model"/> 
    </typeAliases>
  • 第二种方式是通过手动的方式,明确为某个类型配置别名。这种方式的配置如下:
<typeAliases>    
  <typeAlias alias="employe" type="com.mybatis.model.Employe" />   
  <typeAlias type="com.mybatis.model.User" /> 
</typeAliases>
  • 下面我们来看一下两种不同的别名配置是怎样解析的
 typeAliasesElement(root.evalNode("typeAliases"));
 private void typeAliasesElement(XNode parent) {
    if (parent != null) {
     
      for (XNode child : parent.getChildren()) {
           // 从指定的包中解析别名和类型的映射
        if ("package".equals(child.getName())) {
            
          String typeAliasPackage = child.getStringAttribute("name");
            // configuration文件中注册他
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } 
           //  从 typeAlias 节点中解析别名和类型的映射 
        else {
            //   获取 alias 和 type 属性值,alias 不是必填项,可为空
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
              // 加载 type 对应的类型
            Class<?> clazz = Resources.classForName(type);
              // 注册别名到类型的映射
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }
  • ** 通过手动的方式注册别名到类型的映射**
  public void registerAlias(Class<?> type) {
      // 别名
    String alias = type.getSimpleName();
      // 从注解中取出别名
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    }
      // 重载方法
    registerAlias(alias, type);
  }


  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // 将别名转成小写
    String key = alias.toLowerCase(Locale.ENGLISH);
    /*
     * 如果 TYPE_ALIASES 中存在了某个类型映射,这里判断当前类型与映射中的类型是否一致,
     * 不一致则抛出异常,不允许一个别名对应两种类型
     */
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
      // 缓存到typeAliases
    typeAliases.put(key, value);
  }

image.png

  • 从指定的包中解析并注册别名

public void registerAliases(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    
     /*
     * 查找包下的父类为 Object.class 的类。
     * 查找完成后,查找结果将会被缓存到resolverUtil的内部集合中。
     */ 
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    // 获取查找结果
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for (Class<?> type : typeSet) {
        // 忽略匿名类,接口,内部类
        if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            // 为类型注册别名 
            registerAlias(type);
        }
    }
  }
  • 我们看看查找指定包下的所有类
private Set<Class<? extends T>> matches = new HashSet();

public ResolverUtil<T> find(ResolverUtil.Test test, String packageName) {
    //将包名转换成文件路径
    String path = this.getPackagePath(packageName);

    try {
        //通过 VFS(虚拟文件系统)获取指定包下的所有文件的路径名,比如com/mybatis/model/Employe.class
        List<String> children = VFS.getInstance().list(path);
        Iterator i$ = children.iterator();

        while(i$.hasNext()) {
            String child = (String)i$.next();
            //以.class结尾的文件就加入到Set集合中
            if (child.endsWith(".class")) {
                this.addIfMatching(test, child);
            }
        }
    } catch (IOException var7) {
        log.error("Could not read package: " + packageName, var7);
    }

    return this;
}

protected String getPackagePath(String packageName) {
    //将包名转换成文件路径
    return packageName == null ? null : packageName.replace('.', '/');
}

protected void addIfMatching(ResolverUtil.Test test, String fqn) {
    try {
        //将路径名转成全限定的类名,通过类加载器加载类名,比如com.mybatis.model.Employe.class
        String externalName = fqn.substring(0, fqn.indexOf(46)).replace('/', '.');
        ClassLoader loader = this.getClassLoader();
        if (log.isDebugEnabled()) {
            log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
        }

        Class<?> type = loader.loadClass(externalName);
        if (test.matches(type)) {
            //加入到matches集合中
            this.matches.add(type);
        }
    } catch (Throwable var6) {
        log.warn("Could not examine class '" + fqn + "'" + " due to a " + var6.getClass().getName() + " with message: " + var6.getMessage());
    }

}

主要有以下几步:

  1. 通过 VFS(虚拟文件系统)获取指定包下的所有文件的路径名,比如 com/mybatis/model/Employe.class
  2. 筛选以.class结尾的文件名
  3. 将路径名转成全限定的类名,通过类加载器加载类名
  4. 对类型进行匹配,若符合匹配规则,则将其放入内部集合中

五 解析 Plugin文件

插件是 MyBatis 提供的一个拓展机制,通过插件机制我们可在 SQL 执行过程中的某些点上做一些自定义操作。比喻分页插件,在SQL执行之前动态拼接语句,我们后面会单独来讲插件机制,先来了解插件的配置。如下:

<plugins>     
  <plugin interceptor="com.github.pagehelper.PageInterceptor">         
  <property name="helperDialect" value="mysql"/>    
  </plugin> 
</plugins>
pluginElement(root.evalNode("plugins"));
/**
   * 解析<plugins>节点
   * @param parent <plugins>节点
   * @throws Exception
   */
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) { // <plugins>节点存在
      for (XNode child : parent.getChildren()) { // 依次<plugins>节点下的取出每个<plugin>节点
        // 读取拦截器类名
        String interceptor = child.getStringAttribute("interceptor");
        // 读取拦截器属性
        Properties properties = child.getChildrenAsProperties();
        // 实例化拦截器类
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        // 设置拦截器的属性
        interceptorInstance.setProperties(properties);
        // 将当前拦截器加入到拦截器链中
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
  • 据别名解析Class,然后创建实例
protected <T> Class<? extends T> resolveClass(String alias) {
    if (alias == null) {
      return null;
    }
    try {
      return resolveAlias(alias);
    } catch (Exception e) {
      throw new BuilderException("Error resolving class. Cause: " + e, e);
    }
  }
  • Intercepto类:这是插件接口,所有插件需要实现该接口
public interface Interceptor {
  /**
   * 该方法内是拦截器拦截到目标方法时的操作
   * @param invocation 拦截到的目标方法的信息
   * @return 经过拦截器处理后的返回结果
   * @throws Throwable
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * 用返回值替代入参对象。
   * 通常情况下,可以调用Plugin的warp方法来完成,因为warp方法能判断目标对象是否需要拦截,并根据判断结果返回相应的对象来替换目标对象
   * @param target MyBatis传入的支持拦截的几个类(ParameterHandler、ResultSetHandler、StatementHandler、Executor)的实例
   * @return 如果当前拦截器要拦截该实例,则返回该实例的代理;如果不需要拦截该实例,则直接返回该实例本身
   */
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  /**
   * 设置拦截器的属性
   * @param properties 要给拦截器设置的属性
   */
  default void setProperties(Properties properties) {
    // NOP
  }

}
  • InterceptorChain:拦截链
ublic class InterceptorChain {
    // 拦截器链
    private final List<Interceptor> interceptors = new ArrayList<>();

    // target是支持拦截的几个类的实例。该方法依次向所有拦截器插入这几个类的实例
    // 如果某个插件真的需要发挥作用,则返回一个代理对象即可。如果不需要发挥作用,则返回原对象即可

    /**
     * 向所有的拦截器链提供目标对象,由拦截器链给出替换目标对象的对象
     * @param target 目标对象,是MyBatis中支持拦截的几个类(ParameterHandler、ResultSetHandler、StatementHandler、Executor)的实例
     * @return 用来替换目标对象的对象
     */
    public Object pluginAll(Object target) {
        // 依次交给每个拦截器完成目标对象的替换工作
        for (Interceptor interceptor : interceptors) {
            target = interceptor.plugin(target);
        }
        return target;
    }

    /**
     * 向拦截器链增加一个拦截器
     * @param interceptor 要增加的拦截器
     */
    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

    /**
     * 获取拦截器列表
     * @return 拦截器列表
     */
    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }

}


  /**
   * 根据拦截器的配置来生成一个对象用来替换被代理对象
   * @param target 被代理对象
   * @param interceptor 拦截器
   * @return 用来替换被代理对象的对象
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    // 得到拦截器interceptor要拦截的类型与方法
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    // 被代理对象的类型
    Class<?> type = target.getClass();
    // 逐级寻找被代理对象类型的父类,将父类中需要被拦截的全部找出
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    // 只要父类中有一个需要拦截,说明被代理对象是需要拦截的
    if (interfaces.length > 0) {
      // 创建并返回一个代理对象,是Plugin类的实例
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    // 直接返回原有被代理对象,这意味着被代理对象的方法不需要被拦截
    return target;
  }

六 解析 Environments 文件

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
      <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
      <property name="username" value="root"/>
      <property name="password" value="123456"/>
    </dataSource>
  </environment>
</environments>
 environmentsElement(root.evalNode("environments"));
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (environment == null) {
            environment = context.getStringAttribute("default");
        }
        for (XNode child : context.getChildren()) {
            // 获取Id属性
            String id = child.getStringAttribute("id");
             
             //检测当前 environment 节点的 id 与其父节点 environments 的属性 default 内容是否一致
            if (isSpecifiedEnvironment(id)) {
                   // 将environment中的transactionManager标签转换为TransactionFactory对象
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                // 将environment中的dataSource标签转换为DataSourceFactory对象
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                 // 数据源
                DataSource dataSource = dsFactory.getDataSource();
                // 创建 DataSource 对象
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                    .transactionFactory(txFactory)
                    .dataSource(dataSource);
                 // configuration设置
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}
  • transactionManagerElement()
  private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
        // 实例化
      TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a TransactionFactory.");
  }
  • dataSourceElement()
  /**
   * 解析配置信息,获取数据源工厂
   * 被解析的配置信息示例如下:
   * <dataSource type="POOLED">
   *   <property name="driver" value="{dataSource.driver}"/>
   *   <property name="url" value="{dataSource.url}"/>
   *   <property name="username" value="${dataSource.username}"/>
   *   <property name="password" value="${dataSource.password}"/>
   * </dataSource>
   *
   * @param context 被解析的节点
   * @return 数据源工厂
   * @throws Exception
   */
  private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      // 通过这里的类型判断数据源类型,例如POOLED、UNPOOLED、JNDI
      String type = context.getStringAttribute("type");
      // 获取dataSource节点下配置的property
      Properties props = context.getChildrenAsProperties();
      // 根据dataSource的type值获取相应的DataSourceFactory对象
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
      // 设置DataSourceFactory对象的属性
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

image.png

七 解析Mapper 文件

 /**
   * 解析mappers节点,例如:
   * <mappers>
   *    <mapper resource="com/github/yeecode/mybatisDemo/UserDao.xml"/>
   *    <package name="com.github.yeecode.mybatisDemo" />
   * </mappers>
   * @param parent mappers节点
   * @throws Exception
   */
  private void mapperElement(XNode parent) throws Exception {

    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 处理mappers的子节点,即mapper节点或者package节点
        if ("package".equals(child.getName())) { // package节点
          // 取出包的路径
          String mapperPackage = child.getStringAttribute("name");
          // 全部加入Mappers中
          configuration.addMappers(mapperPackage);
        } else {
          // resource、url、class这三个属性只有一个生效
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            // 获取文件的输入流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 使用XMLMapperBuilder解析Mapper文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            // 从网络获得输入流
            InputStream inputStream = Resources.getUrlAsStream(url);
            // 使用XMLMapperBuilder解析Mapper文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            // 配置的不是Mapper文件,而是Mapper接口
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

下一文章我会详细介绍一下Mapper的解析,其他不重要文件的解析,其实更上面有些解析类似,或者很简单就不一一解释了
当这些文件解析完毕,就返回一个Configuration对象,最终我们拿到了SqlSessionFactory

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

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

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

相关文章

vscode无法读取头文件问题,改变exe生成路径问题,头文件,源文件分离问题

首先声明一点&#xff0c;我这里演示的基础是源文件都位于项目根目录下的&#xff0c;然后exe文件和头文件可以放在项目的其他任何文件夹中的。 目录 1. vscode自定义头文件包含问题 1.1 首先是我们的c_cpp_peoperties.json文件 1.2 接下来是我们的settings.json文件 2.改变…

String(2)

字符串中的库函数&#xff0c;基本上只要对原来的字符串进行修改&#xff0c;基本上返回的都是一个新的对象 我们先来看一个内存布局图: String s1new String("hello"); String s2new String("world"); String s3s1; 下列程序的输出结果是:输出good和gbc p…

Go开发环境搭建

文章目录Go环境搭建开发工具Visual Studio Code Go 扩展插件时间2022-12-15 Go环境搭建 下载安装Go 国外的官网: https://golang.org/dl/国内的官网: https://golang.google.cn/dl/ 检查版本go version 必须在安装目录执行命令&#xff1a;C:\Program Files\Go>go version…

软考信息安全工程师备考笔记

上午 信息安全的5个基本要素为&#xff1a;机密性、完整性、可用性、可控性、可审查性。 法律 《中华人民共和国网络安全法》已由中华人民共和国第十二届全国人民代表大会常务委员会第二十四叫次会议于2016年11月7日通过&#xff0c;自2017年6月1日起施行。 第八条明确规定了…

ADI Blackfin DSP处理器-BF533的开发详解53:CMOS摄像头采集图像+LCD显示(含源码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 功能介绍 代码实现了摄像头拍摄一张 640480 大小&#xff0c;YUV422 格式的图片&#xff0c;然后将图片转成 RGB565 格式&#xff0c;裁剪为 480…

Latex 中插入 Matlab 代码

这篇文章将介绍如何在 Latex 排版过程中添加 Matlab 代码 功能效果 主要有如下排版功能&#xff1a; 语法高亮自动添加边框自动添加行号 先上图&#xff0c;大家感受一下效果&#xff1a; 而实现这些只需要一行代码加一个包&#xff01; 插入代码块 \usepackage{listings…

JavaScript刷LeetCode拿offer-二叉树层序遍历篇

前言 博主最近在刷leetcode&#xff0c;做到二叉树套题的时候发现很多题的解题思路都是基于二叉树的层序遍历来完成的&#xff0c;因此写下这篇文章&#xff0c;记录一下二叉树层序遍历这件"神器"在实战的运用。 [leetcode] 102.二叉树的层序遍历 二叉树的层序遍历…

[附源码]Python计算机毕业设计高校宿舍管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

Vue渲染器(五):快速diff算法

渲染器&#xff08;五&#xff09;&#xff1a;快速diff算法 这章开始讨论第三种用于比较新旧vnode的方式&#xff1a;快速diff算法。跟它的名字一样&#xff0c;它很快。Vue3采用的就是这种算法&#xff0c;Vue2采用的是上一章中的双端diff算法。 接下来就来着重了解它的实现…

宝塔部署前后端分离项目(Vue+SpringBoot)

目录 后端部分 配置Redis 前端部分 后端部分 1 先修改自己的speingboot配置文件&#xff0c;我的是yml文件 保证宝塔上建的数据库和自己代码里&#xff0c;就是配置文件中所建的数据库的名字是一致的密码也要保持一致&#xff0c;Redis也一样&#xff0c;如果有的话 2 记录…

关注电动汽车能效水平 提高续航能力

电动汽车&#xff08;EV&#xff09;近些年发展迅猛&#xff0c;已被汽车业内普遍认为是未来汽车发展的新方向&#xff0c;但是现如今电动汽车仍然存在一些短板&#xff0c;导致其还无法替代传统燃油车。对此&#xff0c;先想到的是电动车的续航问题。其实解决电动车续航问题主…

python 插值处理一维数据 interpolate

scipy库&#xff1a; 原码&#xff1a; https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html kind可选“linear”、“nearest”、“nearest-up”、“zero”、“slinear”、“quadratic”、“cubic”、“previous”或“next”之一。 “zero…

JSP sshOA办公系统myeclipse开发oracle数据库MVC模式java编程计算机网页设计

一、源码特点 JSP sshOA办公系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开 发。开发环境为TOMCAT7.0,Myecl…

音视频编解码流程与如何使用 FFMPEG 命令进行音视频处理

一、前言 FFMPEG 是特别强大的专门用于处理音视频的开源库。你既可以使用它的 API 对音视频进行处理&#xff0c;也可以使用它提供的工具&#xff0c;如 ffmpeg, ffplay, ffprobe&#xff0c;来编辑你的音视频文件。 本文将简要介绍一下 FFMPEG 库的基本目录结构及其功能&#…

OpenWrt 安装 WireGuard

下载 img镜像 https://downloads.openwrt.org/releases/22.03.2/targets/x86/generic/openwrt-22.03.2-x86-generic-generic-squashfs-combined-efi.img.gz解压 转化格式我的是Linux # 解压 gzip -d openwrt-22.03.2-x86-generic-generic-squashfs-combined-efi.img.gz #vmwa…

如何将数学曲线变为机器人轨迹-花式show爱心代码-turtlesim篇

第一步&#xff1a;找到曲线数学描述的网址。 阅读后了解曲线所对应的xy函数。 不要选太复杂的&#xff0c;毕竟先复现出来最重要的。 第二步&#xff0c;这个函数转为C代码。 //Lovegoal_x5.54.0*pow(sin(curve_t/200.0),3);goal_y5.5((13.0*cos(curve_t/200.0)-5.0*cos(curv…

网络工程师备考6章

6.1 OSI参考模型概述 计算机的整套理论是图灵提出来的,自此创办图灵奖(计算机类最高奖项)。科学远远比技术更重要。 OSI七层模型就是科学,就是理论,所以非常重要! 注:ISO是一个机构,OSI是一个协议:分别七层 6.2 OSI参考模型 注:在传输层中,什么是端到端,例如A,…

jsp+ssm计算机毕业设计ssm校园贫困补助系统【附源码】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JSPSSM mybatis Maven等等组成&#xff0c;B/S模式 Mave…

使用vite构建vue3项目与官网构建区别

使用vite构建vue3项目 一、vue3官网文档的 https://www.javascriptc.com/vue3js/guide/installation.html#命令行工具-cli npm init vite-app cd npm installnpm run dev 二、vite官网文档的 https://cn.vitejs.dev/guide/#trying-vite-online 1 对应路径cmd 输入 npm create…

Grad-CAM简介-网络 热力图分析

论文名称&#xff1a;Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization 论文下载地址&#xff1a;https://arxiv.org/abs/1610.02391 推荐代码&#xff08;Pytorch&#xff09;&#xff1a;https://github.com/jacobgil/pytorch-grad-cam bi…