spring源码解读之 JdbcTemplate源码

时间:2022-09-30 20:50:29

原文:https://blog.csdn.net/songjinbin/article/details/19857567

在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数据库,在JdbcTemplate为用户程序提供了许多便利的数据库操作方法,比如查询,更新等,而且在Spring中,有许多类似 JdbcTemplate的模板,比如HibernateTemplate等等 - 看来这是Rod.Johnson的惯用手法,

所谓模板板式,就是在父类中定义算法的主要流程,而把一些个性化的步骤延迟到子类中去实现,父类始终控制着整个流程的主动权,子类只是辅助父类实现某些可定制的步骤。

我们用代码来说话吧: 
首先,父类要是个抽象类:

Java代码

public abstract class TemplatePattern {  

    //模板方法
public final void templateMethod(){ method1();
method2();//勾子方法
method3();//抽象方法
}
private void method1(){
System.out.println("父类实现业务逻辑");
}
public void method2(){
System.out.println("父类默认实现,子类可覆盖");
}
protected abstract void method3();//子类负责实现业务逻辑
}

父类中有三个方法,分别是method1(),method2()和method3()。 
method1()是私有方法,有且只能由父类实现逻辑,由于方法是private的,所以只能父类调用。 
method2()是所谓的勾子方法。父类提供默认实现,如果子类觉得有必要定制,则可以覆盖父类的默认实现。 
method3()是子类必须实现的方法,即制定的步骤。 
由此可看出,算法的流程执行顺序是由父类掌控的,子类只能配合。

下面我们来写第一个子类: 
Java代码

public class TemplatePatternImpl extends TemplatePattern {  

    @Override
protected void method3() {
System.out.println("method3()在子类TemplatePatternImpl中实现了!!"); } }

这个子类只覆盖了必须覆盖的方法,我们来测试一下: 
Java代码

TemplatePattern t1 = new TemplatePatternImpl();
t1.templateMethod();

在控制台中我们可以看到: 
Java代码  
父类实现业务逻辑  
父类默认实现,子类可覆盖  
method3()在子类TemplatePatternImpl中实现了!!

OK,我们来看看勾子方法的使用: 
定义第2个子类,实现勾子方法: 
Java代码

public class TemplatePatternImpl2 extends TemplatePattern {  

    @Override
protected void method3() {
System.out.println("method3()在子类TemplatePatternImpl2中实现了!!"); } /* (non-Javadoc)
* @see com.jak.pattern.template.example.TemplatePattern#method2()
*/
@Override
public void method2() {
System.out.println("子类TemplatePatternImpl2覆盖了父类的method2()方法!!");
} }

来测试一下: 
Java代码

TemplatePattern t2 = new TemplatePatternImpl2();
t2.templateMethod();

我们看控制台: 
Java代码  
父类实现业务逻辑  
子类TemplatePatternImpl2覆盖了父类的method2()方法!!  
method3()在子类TemplatePatternImpl2中实现了!!

OK,经典的模板模式回顾完了(大家不要拍砖哦~~~~~~~~~~)

接下来,我们回到正题,自己模仿spring动手写一个基于模板模式和回调的jdbcTemplate。

回顾一下,spring为什么要封装JDBC API,对外提供jdbcTemplate呢(不要仍鸡蛋啊¥·%¥#%) 
话说SUN的JDBC API也算是经典了,曾经在某个年代折服了一批人。但随着历史的发展,纯粹的JDBC API已经过于底层,而且不易控制,由开发人员直接接触JDBC API,会造成不可预知的风险。还有,数据连接缓存池的发展,也不可能让开发人员去手工获取JDBC了。

好了,我们来看一段曾经堪称经典的JDBC API代码吧: 
Java代码

public List<User> query() {  

    List<User> userList = new ArrayList<User>();
String sql = "select * from User"; Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
con = HsqldbUtil.getConnection();
pst = con.prepareStatement(sql);
rs = pst.executeQuery(); User user = null;
while (rs.next()) { user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
} } catch (SQLException e) {
e.printStackTrace();
}finally{
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(!con.isClosed()){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} catch (SQLException e) {
e.printStackTrace();
} }
return userList;
}

上面的代码要若干年前可能是一段十分经典的,还可能被作为example被推广。但时过境迁,倘若哪位程序员现在再在自己的程序中出现以上代码,不是说明该公司的开发框架管理混乱,就说明这位程序员水平太“高”了。 
我们试想,一个简单的查询,就要做这么一大堆事情,而且还要处理异常,我们不防来梳理一下: 
1、获取connection 
2、获取statement 
3、获取resultset 
4、遍历resultset并封装成集合 
5、依次关闭connection,statement,resultset,而且还要考虑各种异常 
6、..... 
啊~~~~ 我快要晕了,在面向对象编程的年代里,这样的代码简直不能上人容忍。试想,上面我们只是做了一张表的查询,如果我们要做第2张表,第3张表呢,又是一堆重复的代码: 
1、获取connection 
2、获取statement 
3、获取resultset 
4、遍历resultset并封装成集合 
5、依次关闭connection,statement,resultset,而且还要考虑各种异常 
6、.....

这时候,使用模板模式的时机到了!!!

通过观察我们发现上面步骤中大多数都是重复的,可复用的,只有在遍历ResultSet并封装成集合的这一步骤是可定制的,因为每张表都映射不同的java bean。这部分代码是没有办法复用的,只能定制。那就让我们用一个抽象的父类把它们封装一下吧: 
Java代码

public abstract class JdbcTemplate {  

    //template method
public final Object execute(String sql) throws SQLException{ Connection con = HsqldbUtil.getConnection();
Statement stmt = null;
try { stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(sql);
Object result = doInStatement(rs);//abstract method
return result;
}
catch (SQLException ex) {
ex.printStackTrace();
throw ex;
}
finally { try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(!con.isClosed()){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} catch (SQLException e) {
e.printStackTrace();
} }
} //implements in subclass
protected abstract Object doInStatement(ResultSet rs);
}

在上面这个抽象类中,封装了SUN JDBC API的主要流程,而遍历ResultSet这一步骤则放到抽象方法doInStatement()中,由子类负责实现。 
好,我们来定义一个子类,并继承上面的父类: 
Java代码

public class JdbcTemplateUserImpl extends JdbcTemplate {  

    @Override
protected Object doInStatement(ResultSet rs) {
List<User> userList = new ArrayList<User>(); try {
User user = null;
while (rs.next()) { user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
return userList;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
} }

由代码可见,我们在doInStatement()方法中,对ResultSet进行了遍历,最后并返回。 
有人可能要问:我如何获取ResultSet 并传给doInStatement()方法啊??呵呵,问这个问题的大多是新手。因为此方法不是由子类调用的,而是由父类调用,并把ResultSet传递给子类的。我们来看一下测试代码: 
Java代码

String sql = "select * from User";
JdbcTemplate jt = new JdbcTemplateUserImpl();
List<User> userList = (List<User>) jt.execute(sql);

就是这么简单!!

文章至此仿佛告一段落,莫急!不防让我们更深入一些...

试想,如果我每次用jdbcTemplate时,都要继承一下上面的父类,是不是有些不方面呢? 
那就让我们甩掉abstract这顶帽子吧,这时,就该callback(回调)上场了

所谓回调,就是方法参数中传递一个接口,父类在调用此方法时,必须调用方法中传递的接口的实现类。

那我们就来把上面的代码改造一下,改用回调实现吧:

首先,我们来定义一个回调接口: 
Java代码

public interface StatementCallback {
Object doInStatement(Statement stmt) throws SQLException;
}

这时候,我们就要方法的签名改一下了: 
Java代码

private final Object execute(StatementCallback action) throws SQLException  

里面的获取数据方式也要做如下修改: 
Java代码

Object result = action.doInStatement(stmt);//abstract method   

为了看着顺眼,我们来给他封装一层吧: 
Java代码

public Object query(StatementCallback stmt) throws SQLException{
return execute(stmt);
}

OK,大功告成! 
我们来写一个测试类Test.java测试一下吧: 
这时候,访问有两种方式,一种是内部类的方式,一种是匿名方式。

先来看看内部类的方式: 
Java代码

//内部类方式
public Object query(final String sql) throws SQLException {
class QueryStatementCallback implements StatementCallback { public Object doInStatement(Statement stmt) throws SQLException {
ResultSet rs = stmt.executeQuery(sql);
List<User> userList = new ArrayList<User>(); User user = null;
while (rs.next()) { user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
return userList; } } JdbcTemplate jt = new JdbcTemplate();
return jt.query(new QueryStatementCallback());
}

在调用jdbcTemplate.query()方法时,传一个StatementCallBack()的实例过去,也就是我们的内部类。

再来看看匿名方式: 
Java代码

//匿名类方式
public Object query2(final String sql) throws Exception{ JdbcTemplate jt = new JdbcTemplate();
return jt.query(new StatementCallback() { public Object doInStatement(Statement stmt) throws SQLException {
ResultSet rs = stmt.executeQuery(sql);
List<User> userList = new ArrayList<User>(); User user = null;
while (rs.next()) { user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
return userList; }
}); }

相比之下,这种方法更为简洁。 
为什么spring不用传统的模板方法,而加之以Callback进行配合呢? 
试想,如果父类中有10个抽象方法,而继承它的所有子类则要将这10个抽象方法全部实现,子类显得非常臃肿。而有时候某个子类只需要定制父类中的某一个方法该怎么办呢?这个时候就要用到Callback回调了。

最后的源码为:

package com.jak.pattern.template.callbacktemplate;
import java.sql.SQLException;
import java.sql.Statement; public interface StatementCallback {
Object doInStatement(Statement stmt) throws SQLException;
}
public class JdbcTemplate {
//template method
private final Object execute(StatementCallback action) throws SQLException{
Connection con = HsqldbUtil.getConnection();
Statement stmt = null;
  try {
  stmt = con.createStatement();
  Object result = action.doInStatement(stmt);//abstract method
  return result;
  }
  catch (SQLException ex) {
    ex.printStackTrace();
    throw ex;
  }
  finally {
    try {
      stmt.close();
    } catch (SQLException e) {
      e.printStackTrace();
    }
    try {
      if(!con.isClosed()){
        try {
          con.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }
}   public Object query(StatementCallback stmt) throws SQLException{   return execute(stmt);
  }
}

//调用测试类

public class Test {

//内部类方式
public Object query(final String sql) throws SQLException {
class QueryStatementCallback implements StatementCallback { public Object doInStatement(Statement stmt) throws SQLException {
ResultSet rs = stmt.executeQuery(sql);
List<User> userList = new ArrayList<User>(); User user = null;
while (rs.next()) { user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
return userList; } } JdbcTemplate jt = new JdbcTemplate();
return jt.query(new QueryStatementCallback());
} //匿名类方式
public Object query2(final String sql) throws Exception{ JdbcTemplate jt = new JdbcTemplate();
return jt.query(new StatementCallback() { public Object doInStatement(Statement stmt) throws SQLException {
ResultSet rs = stmt.executeQuery(sql);
List<User> userList = new ArrayList<User>(); User user = null;
while (rs.next()) { user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
return userList; }
}); }
public static void main(String[] args) throws Exception { String sql = "select * from User";
Test t = new Test(); List<User> userList = (List<User>) t.query(sql);
List<User> userList2 = (List<User>) t.query2(sql);
System.out.println(userList);
System.out.println(userList2);
}
}

言归正传,有了上面的基础后,我们正式开始阅读源码:

下面几个接口是对变化的部分进行建模

接口:创建PreparedStatement。根据Connection来创建PreparedStatement。

public interface PreparedStatementCreator {
PreparedStatement createPreparedStatement (Connection conn)
throws SQLException;
} 使用方法就是: PreparedStatementCreator psc = new PreparedStatementCreator() { public PreparedStatement createPreparedStatement (Connection conn)
throws SQLException {
PreparedStatement ps = conn. prepareStatement (
"SELECT seat_id AS id FROM available_seats WHERE " +
"performance_id = ? AND price_band_id = ?");
ps.setInt(1, performanceId);
ps.setInt(2, seatType);
return ps;
}
};

给PreparedStatement设置参数。是对PreparedStatmentCreator的设置ps值的一个补充。

public interface PreparedStatementSetter {
void setValues(PreparedStatement ps) throws SQLException;
}

对ResultSet进行处理。还有具体的子类。

public interface RowCallbackHandler {
void processRow(ResultSet rs) throws SQLException;
}

使用方式:

 RowCallbackHandler rch = new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
int seatId = rs.getInt(1) ;
list.add(new Integer (seatId) );//典型的inner class的应用,list为外部类的变量。
}
};

和上面的RowCallbackHandler类似。

public interface ResultSetExtractor {
Object extractData(ResultSet rs) throws SQLException, DataAccessException;
}

下面是JdbcTemplate中提供的模板方法。该方法完成对数据库的查询:)。

这个execute()方法非常关键。

public Object query(
PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
throws DataAccessException { Assert.notNull(rse, "ResultSetExtractor must not be null"); if (logger.isDebugEnabled()) {
String sql = getSql(psc); //取得不变的SQL部分。
logger.debug("Executing SQL query" + (sql != null ? " [" + sql + "]" : ""));
}
return execute(psc, new PreparedStatementCallback() {
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
ResultSet rs = null;
try {
if (pss != null) {
pss.setValues(ps);//就是给ps来设置参数用的。ps.setInt(1, 0);
} rs = ps.executeQuery();//执行查询 ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
return rse.extractData(rsToUse); // ResultSetExtractor从ResultSet中将值取出List。 }
finally {
//最后的善后工作还是需要做好的:) rs.close(),把ps的相关参数清除掉。
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}

看看execute()方法吧。
java 代码

public Object execute(PreparedStatementCreator psc, PreparedStatementCallback action)
throws DataAccessException { Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null"); //取得数据库的连接
Connection con = DataSourceUtils.getConnection(getDataSource());
PreparedStatement ps = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
//创建PreparedStatement
ps = psc.createPreparedStatement(conToUse); applyStatementSettings(ps);//这个方法是设置ps的一些属性,我平时不用,Spring框架倒是考虑得相当全的说。 PreparedStatement psToUse = ps;
if (this.nativeJdbcExtractor != null) {
psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
}
//调用Callback来完成PreparedStatement的设值。就是调用上面的doInPreparedStatement来使用ps。
Object result = action.doInPreparedStatement(psToUse); SQLWarning warning = ps.getWarnings();
throwExceptionOnWarningIfNotIgnoringWarnings(warning);
return result;
}
//如果有错误的话,那么就开始ps.close(), connection.close();
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
String sql = getSql(psc);
psc = null;
JdbcUtils.closeStatement(ps); //就是ps.close();
ps = null;
DataSourceUtils.releaseConnection(con, getDataSource()); /
con = null;
throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex);
}

//不管怎么样,ps.close(), Connection.close()吧,当然这里是releaseConnection。在我的程序中,Connection只有一个,没有ConnectionPool,当然不会去close Connection。一般来讲,如果没有Connection的线程池的话,我们肯定也不会经常的关闭Connection,得到Connection。毕竟这个东西非常耗费资源。

  finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}

JdbcTemplate完成了负责的操作,客户只需要调用query()就可以完成查询操作了。当然,JdbcTemplate会实现很多带其它参数的方法,以方便你的使用。Template设计模式被发扬广大了。
DataSourceUtils:这个专门用于管理数据库Connection的类。

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
~~~~~~ //这个方法很舒服,Spring Framework中到处有这样的方法。为什么要委派到这个动作方法?
}
catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
}

这里的doGetConnection就稍微复杂一点了。但是如果没有事务同步管理器的话,那就比较简单。

只是在Connection上多了一个ConnecionHolder类用于持有Connection,实现ConnectionPool的一点小功能。

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
~~~~~//Connection的持有器。通过持有器得到Connection。
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here. logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();
……
return con;
} ConnectionHolder:Connection的持有器。通过ConnectionHandler来完成对Connection的操作:) 典型的委派。 public class ConnectionHolder extends ResourceHolderSupport { private Connection currentConnection; //当前的Connection
private ConnectionHandle connectionHandle; //Connection的处理器,因此可以通过该类完成对connection的管理。 public ConnectionHolder(Connection connection) {
this.connectionHandle = new SimpleConnectionHandle(connection);
} public ConnectionHolder(ConnectionHandle connectionHandle) {
Assert.notNull(connectionHandle, "ConnectionHandle must not be null");
this.connectionHandle = connectionHandle;
} public Connection getConnection() {
Assert.notNull(this.connectionHandle, "Active Connection is required");
if (this.currentConnection == null) {
this.currentConnection = this.connectionHandle.getConnection();
}
return this.currentConnection;
} public void released() {
super.released();
if (this.currentConnection != null) {
this.connectionHandle.releaseConnection(this.currentConnection);
this.currentConnection = null;
}
} connectionHandle 的接口太纯粹了。但是我觉得这个设计太过于细致了:) public interface ConnectionHandle { /**
* Fetch the JDBC Connection that this handle refers to.
*/
Connection getConnection(); /**
* Release the JDBC Connection that this handle refers to.
* @param con the JDBC Connection to release
*/
void releaseConnection(Connection con); }

最后看一下SimpleConnectionHandle,这个ConnectionHandle的简单实现类。就只有一个Connection可管理。如果有多个Connection可管理的话,这里就是ConnectionPool了:)

java 代码

public class SimpleConnectionHandle implements ConnectionHandle {  

    private final Connection connection;  

    /**
* Create a new SimpleConnectionHandle for the given Connection.
* @param connection the JDBC Connection
*/
public SimpleConnectionHandle(Connection connection) {
Assert.notNull(connection, "Connection must not be null");
this.connection = connection;
} /**
* Return the specified Connection as-is.
*/
public Connection getConnection() {
return connection;
} /**
* This implementation is empty, as we're using a standard
* Connection handle that does not have to be released.
*/
public void releaseConnection(Connection con) {
} public String toString() {
return "SimpleConnectionHandle: " + this.connection;
} }

spring源码解读之 JdbcTemplate源码的更多相关文章

  1. go语言 nsq源码解读三 nsqlookupd源码nsqlookupd&period;go

    从本节开始,将逐步阅读nsq各模块的代码. 读一份代码,我的思路一般是: 1.了解用法,知道了怎么使用,对理解代码有宏观上有很大帮助. 2.了解各大模块的功能特点,同时再想想,如果让自己来实现这些模块 ...

  2. DRF&lpar;1&rpar; - REST、DRF&lpar;View源码解读、APIView源码解读&rpar;

    一.REST 1.什么是编程? 数据结构和算法的结合. 2.什么是REST? 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下: /books/ /get_all_books/ 访问所 ...

  3. REST、DRF&lpar;View源码解读、APIView源码解读&rpar;

    一 . REST            前言 1 . 编程 : 数据结构和算法的结合 .小程序如简单的计算器,我们输入初始数据,经过计算,得到最终的数据,这个过程中,初始数据和结果数据都是数据,而计算 ...

  4. Restful 1 -- REST、DRF&lpar;View源码解读、APIView源码解读&rpar;及框架实现

    一.REST 1.什么是编程? 数据结构和算法的结合 2.什么是REST? - url用来唯一定位资源,http请求方式来区分用户行为 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下 ...

  5. go语言 nsq源码解读四 nsqlookupd源码options&period;go、context&period;go和wait&lowbar;group&lowbar;wrapper&period;go

    本节会解读nsqlookupd.go文件中涉及到的其中三个文件:options.go.context.go和wait_group_wrapper.go. options.go 123456789101 ...

  6. go语言nsq源码解读五 nsqlookupd源码registration&lowbar;db&period;go

    本篇将讲解registration_db.go文件. 1234567891011121314151617181920212223242526272829303132333435363738394041 ...

  7. php-msf 源码解读【转】

    php-msf: https://github.com/pinguo/php-msf 百度脑图 - php-msf 源码解读: http://naotu.baidu.com/file/cc7b5a49 ...

  8. spring jdbcTemplate源码剖析

    本文浅析 spring jdbcTemplate 源码,主要是学习其设计精髓.模板模式.巧妙的回调 一.jdbcTemplate 类结构 ①.JdbcOperations : 接口定义了方法,如 &l ...

  9. Spring源码-循环依赖源码解读

    Spring源码-循环依赖源码解读 笔者最近无论是看书还是从网上找资料,都没发现对Spring源码是怎么解决循环依赖这一问题的详解,大家都是解释了Spring解决循环依赖的想法(有的解释也不准确,在& ...

随机推荐

  1. Magic xpa 2&period;5发布 Magic xpa 2&period;5 Release Notes

    Magic xpa 2.5發佈 Magic xpa 2.5 Release Notes Magic xpa 2.5 Release NotesNew Features, Feature Enhance ...

  2. iOS 三种收起键盘的方法

    - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typica ...

  3. MVC Pager 使用

    MVC Pager  4.0+     3.0版本使用  ,直接来点使用的.一看就明白 @Ajax.Pager(Model,pagerOptions,mvcAjaxOptions); @using W ...

  4. Ubuntu系统如何查看硬件配置信息

    查看ubuntu硬件信息 1, 主板信息 .查看主板的序列号 -------------------------------------------------- #使用命令 dmidecode | ...

  5. java 产生随机数的方法

    有三种方法: Math.random():这个方法返回一个[0.0, 1.0)的一个随机double型数.它实际是调用Random类的nextDouble()方法.只不过Math类使用的是一个静态随机 ...

  6. 2&period;Redis的基本配置

    一.参数配置 redis.conf的主要配置参数的意义: daemonize:是否以后台daemon方式运行 pidfile:pid文件位置 port:监听的端口号 timeout:请求超时时间 lo ...

  7. python练习四—简单的聊天软件

    python最强大的是什么?库支持!!有了强大的库支持,一个简单的聊天软件实现就更简单了,本项目思路如下 # 项目思路 1. 服务器的工作 * 初始化服务器 * 新建一个聊天房间 * 维护一个已链接用 ...

  8. IOS-网络(AFNetworking)

    一.AFNetWorking基本使用 // // ViewController.m // IOS_0112_AFNetWorking // // Created by ma c on 16/2/11. ...

  9. 【bzoj1834】&lbrack;ZJOI2010&rsqb;network 网络扩容

    1834: [ZJOI2010]network 网络扩容 Time Limit: 3 Sec  Memory Limit: 64 MBSubmit: 2701  Solved: 1368[Submit ...

  10. jquery lazyload延迟加载技术的实现原理分析&lowbar;jquery

    前言 懒加载技术(简称lazyload)并不是新技术,它是js程序员对网页性能优化的一种方案.lazyload的核心是按需加载.在大型网站中都有lazyload的身影,例如谷歌的图片搜索页,迅雷首页, ...