Mybatis一级缓存和结合Spring Framework后失效的源码探究

  1.在下面的案例中,执行两次查询控制台只会输出一次 SQL 查询:

mybatis-config.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">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true"/>
                <property name="username" value="xxx"/>
                <property name="password" value="xxx"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/hrh/mapper/PersonMapper.xml"/>
    </mappers>
</configuration>
PersonMapper.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.hrh.mapper.PersonMapper">
    <resultMap id="BaseResultMap" type="com.hrh.bean.Person">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <result column="age" property="age" jdbcType="BIGINT"/>
    </resultMap>
    <sql id="Base_Column_List">
    id, name, age
    </sql>
    <select id="list" resultType="com.hrh.bean.Person">
        select
        <include refid="Base_Column_List"/>
        from tab_person
    </select>
</mapper>
public interface PersonMapper {
     List<Person> list();
}
String resource = "mybatis-config2.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();//开启会话
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
        mapper.list();
        mapper.list();

  之所以会出现这种情况,是因为 Mybatis 存在一级缓存导致的,下面 debug 探究下内部流程:

  (1)mapper.list() 会进入 MapperProxy#invoke():参数proxy是一个代理对象(每个 Mapper 接口都会被转换成一个代理对象),里面包含会话 sqlSession、接口信息、方法信息;method是目标方法(当前执行的方法),它里面包含了所属的哪个类(接口)、方法名、返回类型(List、Map、void 或其他)、参数类型等;args是参数;

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //从方法缓存methodCache中获取到方法的信息:比如方法名、类型(select、update等)、返回类型
    //如果获取中没有MapperMethod,则创建一个并放入methodCache中
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //执行查询SQL并返回结果
    return mapperMethod.execute(sqlSession, args);
  }

  

  cacheMapperMethod:MapperMethod 包含方法名、类型(select、update等)、返回类型等信息

private MapperMethod cachedMapperMethod(Method method) {
    //缓存中获取
    MapperMethod mapperMethod = methodCache.get(method);
    //没有则创建一个对象并放入缓存中供下次方便取用
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

  (2)MapperMethod#execute()根据 SQL 类型进入不同的查询方法

public Object execute(SqlSession sqlSession, Object[] args) {
    //返回结果
    Object result;
    //判断语句类型
    switch (command.getType()) {
      case INSERT: {//插入语句
      Object param = method.convertArgsToSqlCommandParam(args);
        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://查询语句
        //返回空的查询
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
          //返回List的查询
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
          //返回Map的查询
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
          //返回游标的查询
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        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;
  }

  (3)上面的案例是 select 语句,返回结果是List集合,所以进入 MapperMethod#executeForMany():

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //获取参数
    Object param = method.convertArgsToSqlCommandParam(args);
    //是否有分页查询
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    //如果list中的泛型跟结果类型不一致,进行转换
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

  (4)selectList执行了DefaultSqlSession#selectList():

 public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //SQL执行的信息:resource(xxMapper.xml)、id、sql、返回类型等
      MappedStatement ms = configuration.getMappedStatement(statement);
      //执行查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  (5)接下来调用缓存执行器的方法:CachingExecutor#query()

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //获取到执行SQL
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //将SQL包装成一个缓存对对象,该对象和结果集组成键值对存储到缓存中,方便下次直接从缓存中拿而不需要再次查询
    //createCacheKey:调用BaseExecutor#createCacheKey
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //获取缓存
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //没有缓存连接查询
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  (6)接下来执行 BaseExecutor#query():从下面可以看到将结果缓存到localCache 中了

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //如果不是嵌套查询(默认为0),且 <select> 的 flushCache=true 时清空缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
     //嵌套查询层数+1
      queryStack++;
      //从localCache缓存中获取
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //连接查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    //下面是延迟加载逻辑
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //缓存中添加占位符
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //连接查询获取到数据结果
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      //删除占位符
      localCache.removeObject(key);
    }
    //将结果缓存起来
    localCache.putObject(key, list);
    //处理存储过程
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

  2.但当 Spring Framework + Mybatis 时,情况就不一样了,每次查询都会连接数据库查询,控制台都会打印 SQL 出来,如下案例:

@Service
public class PersonService  {
    @Autowired
    PersonMapper personMapper;

    public List<Person> getList() {
        personMapper.list();
        personMapper.list();
        return personMapper.list();
    }
}
@Configuration
@ComponentScan("com.hrh")
@MapperScan("com.hrh.mapper")
public class MyBatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        factoryBean.setMapperLocations(resolveMapperLocations());
        return factoryBean;
    }

    public Resource[] resolveMapperLocations() {
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        List<String> mapperLocations = new ArrayList<>();
        mapperLocations.add("classpath*:com/hrh/mapper/*Mapper*.xml");
        List<Resource> resources = new ArrayList();
        if (mapperLocations != null) {
            for (String mapperLocation : mapperLocations) {
                try {
                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
                    resources.addAll(Arrays.asList(mappers));
                } catch (IOException e) {
                    // ignore
                }
            }
        }
        return resources.toArray(new Resource[resources.size()]);
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        driverManagerDataSource.setUsername("xxx");
        driverManagerDataSource.setPassword("xxx");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true");
        return driverManagerDataSource;
    }
}
  AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);
        PersonService bean = context.getBean(PersonService.class);
        bean.getList();

  下面debug进入的步骤跟上面的(1)、(2)、(3)是一致的,但第四步却是进入SqlSessionTemplate#selectList()中【SqlSessionTemplate是mybatis-spring-xx.jar的,上文的DefaultSqlSession是属于mybatis-xx.jar的】:

  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  接下来的selectList() 会被方法拦截:method.invoke() 会执行到 DefaultSqlSession#selectList(),重新回到上文的第四步并且继续下去,也就是在上文的(1)~(6)中插入了前后文,在其中做了关闭会话的操作;

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //得到会话
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //执行方法查询
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);//在关闭会话前提交和回滚
        }
        return result;

      } catch (Throwable t) {//有异常抛出异常并结束会话
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        //关闭会话
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

总结:

  Mybatis 的一级缓存是会话级别的缓存(单线程的,特别鸡肋),Mybatis 每创建一个 SqlSession 会话对象,就表示打开一次数据库会话,在一次会话中,应用程序很可能在短时间内反复执行相同的查询语句,如果不对数据进行缓存,则每查询一次就要执行一次数据库查询,这就造成数据库资源的浪费。又因为通过 SqlSession 执行的操作,实际上由 Executor 来完成数据库操作的,所以在 Executor 中会建立一个简单的缓存,即一级缓存;将每次的查询结果缓存起来,再次执行查询的时候,会先查询一级缓存(默认开启的),如果命中,则直接返回,否则再去查询数据库并放入缓存中。

  一级缓存的生命周期与 SqlSession 的生命周期相同,因此当 Mybatis 和Spring Framework 的集成包中扩展了一个 SqlSessionTemplate 类(它是一个代理类,增强了查询方法),所有的查询经过 SqlSessionTemplate 代理拦截后再进入到 DefaultSqlSession#selectList() 中,结束查询后把会话SqlSession 关了,所以导致了缓存失效。

  那为什么要这么操作呢?

  原始的 Mybatis 有暴露 SqlSession 接口,因此有 close 方法暴露出来供你选择使用,你可以选择关与不关,但在Mybatis 和Spring Framework 的集成包中,SqlSession 是交给了Spring Framework 管理的,没有暴露出来,为了稳妥决定,直接给你关了。

到此这篇关于Mybatis一级缓存和结合Spring Framework后失效的源码探究的文章就介绍到这了,更多相关Mybatis一级缓存Spring Framework失效内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MyBatis一级缓存避坑完全指南

    一级缓存概念 当我们使用Mybatis进行数据库的操作时候,会创建一个SqlSession来进行一次数据库的会话,会话结束则关闭SqlSession对象.那么一个SqlSession的生命周期即对应于Mybatis的一次会话.在Mybatis的一次会话中,我们很有可能多次查询完全相同的sql语句,如果不采取措施的话,每一次查询都查询一次数据库.而一次会话时间一般都是极短的,相同Sql的查询结果极有可能完全相同.由于查询数据库代价是比较大的,这会导致系统的资源浪费. 为了解决这个问题,Mybati

  • Spring Framework 5.0 入门教程

    1. 为什么学习Spring? 随着对Java EE的不断接触和理解,你会发现Spring 在各个企业和项目中发挥着越来越重要的作用.掌握Spring 已成为我们IT行业生存必学的本领之一. Spring Framework 是一个开源的Java/Java EE全功能栈(full-stack)的应用程序框架,以Apache许可证形式发布,也有.NET平台上的移植版本. 该框架基于 Expert One-on-One Java EE Design and Development(ISBN 0-76

  • SpringBoot + Mybatis-plus实战之Mybatis-plus的一级缓存、二级缓存

    前言 现在的JAVA行业,貌似已经是SpringBoot + SpringCloud 的天下了,早期的SSH,SSM框架已经老去,与SpringBoot相结合的JPA框架虽然省去了很多的增删改查sql,但是比较笨拙,在面对一些复杂多变的逻辑时常常力不从心,而相对应的Mybatis由于其高度的灵活性受到广大JAVA攻城狮的欢迎.之前整合过了springboot+mybatis,前几天看到一个面试的问一个问题,Mybatis的一级缓存,二级缓存.我想这个应该也是一个重点吧,所以今天决定来详细解读一下

  • 深入理解Mybatis一级缓存

    客户端向数据库服务器发送同样的sql查询语句,如果每次都去访问数据库,会导致性能的降低. 那么怎么提高呢? mybatis为我们提供了一级缓存的策略 在一个sqlSession开启和关闭之间,sqlSession对象内部(其实是Executor)会维护一个缓存的对象,当查询数据时候,先从缓存中寻找是否存在该条数据,存在就直接取出来,不存在,向数据库发送sql查询, 然后将查询后的数据存入缓存,和返回给程序. 这样会存在一个问题: 如果在第一次和第二次查询期间,有程序更改了要查讯的数据库的数据,就

  • 解决spring结合mybatis时一级缓存失效的问题

    之前了解到mybatis的一级缓存是默认开启的,作用域是sqlSession,是基 HashMap的本地缓存.不同的SqlSession之间的缓存数据区域互不影响. 当进行select.update.delete操作后并且commit事物到数据库之后,sqlSession中的Cache自动被清空 <setting name="localCacheScope" value="SESSION"/> 结论 spring结合mybatis后,一级缓存作用: 在未

  • mybatis教程之查询缓存(一级缓存二级缓存和整合ehcache)

    1 缓存的意义 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题. 2 mybatis持久层缓存 mybatis提供一级缓存和二级缓存 mybatis一级缓存是一个SqlSession级别,sqlsession只能访问自己的一级缓存的数据,二级缓存是跨sqlSession,是mapper级别的缓存,对于mapper级别的缓存不同的sqlsession是可以共享的. 3 一级缓存 3.1 原

  • MyBatis 延迟加载、一级缓存、二级缓存(详解)

    使用ORM框架我们更多的是使用其查询功能,那么查询海量数据则又离不开性能,那么这篇中我们就看下mybatis高级应用之延迟加载.一级缓存.二级缓存.使用时需要注意延迟加载必须使用resultMap,resultType不具有延迟加载功能. 一.延迟加载 延迟加载已经是老生常谈的问题,什么最大化利用数据库性能之类之类的,也懒的列举了,总是我一提到延迟加载脑子里就会想起来了Hibernate get和load的区别.OK,废话少说,直接看代码. 先来修改配置项xml. 注意,编写mybatis.xm

  • 深入理解MyBatis中的一级缓存与二级缓存

    前言 先说缓存,合理使用缓存是优化中最常见的,将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能. 一级缓存 一级缓存是SqlSession级别的缓存.在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据.不同的sqlSession之间的缓存数据区域是互相不影响的.也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的. 一级缓

  • 简单了解Spring Framework5.0新特性

    SpringFramework5.0是自2013年12月版本4发布之后SpringFramework的第一个主发行版.SpringFramework项目的领导人JuergenHoeller于2016年7月28日宣布了第一个SpringFramework5.0里程碑版本(5.0M1). 现在,将近一年的时间过去以后,我们期盼已久的RC3版本将于2017年7月18日发行.这是路线图规划中SpringFramework5.0首个GA发行版的最后一次发行. 从高层来看,SpringFramework5.

  • Mybatis一级缓存和结合Spring Framework后失效的源码探究

    1.在下面的案例中,执行两次查询控制台只会输出一次 SQL 查询: mybatis-config.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"

  • Mybatis 一级缓存与二级缓存的实现

    mybatis缓存 mybatis作为一个流行的持久化工具,缓存必然是缺少不了的组件.通过这篇文章,就让我们来了解一下mybatis的缓存. mybatis缓存类型 说起mybatis的缓存,了解过的同学都知道,mybatis中可以有两种缓存类型: 第一种,我们通常称为以及缓存,或者sqlSession级别的缓存,这种缓存是mybatis自带的,如果mapper中的配置都是默认的话,那么一级缓存也是默认开启的. 第二种,就是非sqlSession级别的缓存了,我们通常称为二级缓存,mybatis

  • MyBatis一级缓存与二级缓存原理与作用分析

    目录 缓存的作用 MyBatis 的缓存结构 一级缓存 二级缓存 缓存的作用 在 Web 系统中,最重要的操作就是查询数据库中的数据.但是有些时候查询数据的频率非常高,这是很耗费数据库资源的,往往会导致数据库查询效率极低,影响客户的操作体验.于是可以将一些变动不大且访问频率高的数据,放置在一个缓存容器中,用户下一次查询时就从缓存容器中获取结果. MyBatis 的缓存结构 MyBatis 系统中默认定义了两级缓存:一级缓存和二级缓存: MyBatis 一级缓存是一个 SqlSession 级别,

  • Spring SpringMVC在启动完成后执行方法源码解析

    关键字:spring容器加载完毕做一件事情(利用ContextRefreshedEvent事件) 应用场景:很多时候我们想要在某个类加载完毕时干某件事情,但是使用了spring管理对象,我们这个类引用了其他类(可能是更复杂的关联),所以当我们去使用这个类做事情时发现包空指针错误,这是因为我们这个类有可能已经初始化完成,但是引用的其他类不一定初始化完成,所以发生了空指针错误,解决方案如下: 1.写一个类继承spring的ApplicationListener监听,并监控ContextRefresh

  • Spring菜鸟教你看源码冲面试

    Spring 类的初始化和实例化的不同 IOC 探究spring的IOC容器 DefaultListableBeanFactory是最终实现类,在代码中可以找到HashMap的影子:IOC容器就是用HashMap装的Bean; public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefin

  • Django Rest Framework实现身份认证源码详解

    目录 一.Django框架 二.身份认证的两种实现方式: 三.身份认证源码解析流程 一.Django框架 Django确实是一个很强大,用起来很爽的一个框架,在Rest Framework中已经将身份认证全都封装好了,用的时候直接导入authentication.py这个模块就好了.这个模块中5个认证类.但是我们在开发中很少用自带的认证类,而是根据项目实际需要去自己实现认证类.下面是内置的认证类 BaseAuthentication(object):所有的认证相关的类都继承自这个类,我们写的认证

  • Spring循环引用失败问题源码解析

    目录 前言: 例子 启动容器 加载circulationa AbstractBeanFactory 最终调用BeanDefinitionValueResolver circulationb加载分析 前言: 之前我们有分析过Spring是怎么解决循环引用的问题,主要思路就是三级缓存: Spring在加载beanA的时候会先调用默认的空构造函数(在没有指定构造函数实例化的前提下)得到一个空的实例引用对象,这个时候没有设置任何值,但是Spring会用缓存把它给提前暴露出来,让其他依赖beanA的bea

  • Spring EnableAsync注解异步执行源码解析

    目录 概述 @EnableAsync 分析 ProxyAsyncConfiguration 分析 AsyncAnnotationBeanPostProcessor 分析 AsyncAnnotationAdvisor 分析 Advice 构建 Pointcut 构建 AnnotationAsyncExecutionInterceptor 分析 AsyncTaskExecutor 查找 Callable 任务封装 doSubmit 异步执行方法 总结 概述 基于 Spring Framework v

随机推荐