复用和扩展是模板模式的主要功能,当然有另外的概念,可以起到相同的作用,就是回调callback,那么我们学习下回调的原理和对应的实现
回调的原理剖析
回调可以分为同步回调和异步回调,对于普通的同步回调来说,回调是一种双向的关系,A类先注册某个函数到B类,然后B类调用自身的某个X函数的时候,反过来调用A类注册的Y函数,这个Y就是回调函数
A调用B注册,B反过来调用A,就是一种回调机制,
在Java中,没法直接传递函数的,只能使用包裹了回调函数的类对象,称为回调对象,
public interface ICallback {
void methodToCallback(); } public class BClass { public void process(ICallback callback) { //… callback.methodToCallback(); //… } } public class AClass { public static void main(String[] args) { BClass b = new BClass(); b.process(new ICallback() { //回调对象 @Override public void methodToCallback() { System.out.println(“Call back me.”); } }); } } |
上面就是Java语言中的回调的典型实现,在代码实现中看出,回调可以进行复用和扩展,
如果我们作为使用者使用AClass,就可以在BClass和ICallback实现我们的定制,作为一个新的扩展点
在一班的第三方支付的时候,都是通过注册回调接口给第三方支付系统,执行完成,在返回给用户
回调可以分为同步和异步回调,同步回调指的是函数返回之前调用回调函数,异步回调指的是函数返回之后执行
其实也没那么细化,就是同步回调就是我这边掉了你了,给你注册函数了,在你执行回调函数前,我是不能继续进行执行的
异步回调则是我去注册到你这里一个函数,我在注册之后不需要关心是否接受回调,我能继续走下去
所以同步回调和模板模式的留有扩展点类似,异步回调和观察者模式相似
那么,我们看下一个同步回调的案例
Spring提供了组多的Template类,尽管叫做Template类,但是并非模板模式实现的,而是基于的回调进行实现的,只不过都是用了Template来作为后缀
那么我们拿JDBCTemplate来进行举例分析
如果不使用Template来直接执行JDBC的话,我们操作数据库是如下的代码
public class JdbcDemo {
public User queryUser(long id) { Connection conn = null; Statement stmt = null; try { //1.加载驱动 Class.forName(“com.mysql.jdbc.Driver”); conn = DriverManager.getConnection(“jdbc:mysql://localhost:3306/demo”, “xzg”, “xzg”); //2.创建statement类对象,用来执行SQL语句 stmt = conn.createStatement(); //3.ResultSet类,用来存放获取的结果集 String sql = “select * from user where id=” + id; ResultSet resultSet = stmt.executeQuery(sql); String eid = null, ename = null, price = null; while (resultSet.next()) { User user = new User(); user.setId(resultSet.getLong(“id”)); user.setName(resultSet.getString(“name”)); user.setTelephone(resultSet.getString(“telephone”)); return user; } } catch (ClassNotFoundException e) { // TODO: log… } catch (SQLException e) { // TODO: log… } finally { if (conn != null) try { conn.close(); } catch (SQLException e) { // TODO: log… } if (stmt != null) try { stmt.close(); } catch (SQLException e) { // TODO: log… } } return null; } } |
需要去手动的加载驱动,连接数据库,关闭连接,处理异常,对于这个样子,Spring提供了JdbcTemplate,对JDBC进行了进一步的封装,来简化数据库编程,对JDBC进行了进一步的封装,来简化了数据库的编程,使用jdbcTemplate来查询用户信息,只需要编写相关的代码,查询的SQL语句,则交给jdbcTemplate类中了
public class JdbcTemplateDemo {
private JdbcTemplate jdbcTemplate; public User queryUser(long id) { String sql = “select * from user where id=”+id; return jdbcTemplate.query(sql, new UserRowMapper()).get(0); } class UserRowMapper implements RowMapper<User> { public User mapRow(ResultSet rs, int rowNum) throws SQLException { User user = new User(); user.setId(rs.getLong(“id”)); user.setName(rs.getString(“name”)); user.setTelephone(rs.getString(“telephone”)); return user; } } } |
那么对于JDBCTemplate底层如何实现呢?其也是利用了回调的机制,将不变的方法抽离出来,execute作为模板方法,并且将可变的部分设计为回调的StatementCallback,由用户设置,query()函数是execute()的二次封装,让借口更加方便的使用
@Override
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException { return query(sql, new RowMapperResultSetExtractor<T>(rowMapper)); } @Override public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(sql, “SQL must not be null”); Assert.notNull(rse, “ResultSetExtractor must not be null”); if (logger.isDebugEnabled()) { logger.debug(“Executing SQL query [” + sql + “]”); } class QueryStatementCallback implements StatementCallback<T>, SqlProvider { @Override public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; try { rs = stmt.executeQuery(sql); ResultSet rsToUse = rs; if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } return rse.extractData(rsToUse); } finally { JdbcUtils.closeResultSet(rs); } } @Override public String getSql() { return sql; } } return execute(new QueryStatementCallback()); } @Override public <T> T execute(StatementCallback<T> action) throws DataAccessException { Assert.notNull(action, “Callback object must not be null”); Connection con = DataSourceUtils.getConnection(getDataSource()); Statement stmt = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } stmt = conToUse.createStatement(); applyStatementSettings(stmt); Statement stmtToUse = stmt; if (this.nativeJdbcExtractor != null) { stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt); } T result = action.doInStatement(stmtToUse); handleWarnings(stmt); return result; } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn’t been initialized yet. JdbcUtils.closeStatement(stmt); stmt = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate(“StatementCallback”, getSql(action), ex); } finally { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } } |
然后在客户端开发中,需要给控件注册事件监听器,在web页面中,给按钮注册事件监听器是很常见的
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { System.out.println(“I am clicked.”); } }); |
这种注册监控器的方式,很像是观察者模式,事先注册一个观察者,然后在点击的时候,发送事件给观察者,并执行相对应的onClick()函数,比较像是观察者模式
最后Jvm中有一种Hook的设计,也可以被认为Callback,两者可以是一回事,也可以说Hook更加注重场景描述,Callback更加注重语法机制的实现
Hook在Tomcat和JVM实现的方式shutdown hook,
JVM提供了RunTime.addShutdownHook(Thread hook),可以注册一个被JVM关闭的Hook,在应用关闭的的时候,自动调用Hook代码
public class ShutdownHookDemo {
private static class ShutdownHook extends Thread { public void run() { System.out.println(“I am called during shutting down.”); } } public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new ShutdownHook()); } } public class Runtime { public void addShutdownHook(Thread hook) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission(“shutdownHooks”)); } ApplicationShutdownHooks.add(hook); } } class ApplicationShutdownHooks { /* The set of registered hooks */ private static IdentityHashMap<Thread, Thread> hooks; static { hooks = new IdentityHashMap<>(); } catch (IllegalStateException e) { hooks = null; } } static synchronized void add(Thread hook) { if(hooks == null) throw new IllegalStateException(“Shutdown in progress”); if (hook.isAlive()) throw new IllegalArgumentException(“Hook already running”); if (hooks.containsKey(hook)) throw new IllegalArgumentException(“Hook previously registered”); hooks.put(hook, hook); } static void runHooks() { Collection<Thread> threads; synchronized(ApplicationShutdownHooks.class) { threads = hooks.keySet(); hooks = null; } for (Thread hook : threads) { hook.start(); } for (Thread hook : threads) { while (true) { try { hook.join(); break; } catch (InterruptedException ignored) { } } } } } |
在关闭的时候,JVM会调用这个类的runHooks()方法,创建多个线程,执行这些个Hook,注册完成Hook后,也不需要停顿在那里,继续执行即可
那么,模板模式和回调之间有什么区别和联系?
从应用场景上来看,回调和模板模式几乎一致,都是在一个大的算法骨架中,通过替换其中的某个步骤,达到代码复用和扩展的目的,但是异步的回调和模板模式有很大的区别,更像是观察者模式
从实现上来看,两者并不太一样,模板模式是基于了继承实现的,回调基于了组合实现的,
当然,组合优于继承,这里也不例外,在代码的实现上,回调相对于模板模式更加的灵活
而回调一般来说优于模板,可以有如下的解释
1.Java这种只支持单继承的语言,对于模板模式编写的子类,只能有着一种实现
2.回调可以通过匿名类来创建回调对象,可以不用事先的定义类,模板模式则需要针对性的实现
3.如果一个类中定义了多个模板方法,每个方法都有对应的抽象方法,那么我们只用一个模板方法,也需要实现所有的抽象方法,而回调更加灵活
那么本章重点讲完了
本章说了回调,也是为了代码复用和扩展的
相比较于普通的函数调用,回调是一种双向的调用关系,A类事先注册一个函数到B类,B类执行这个函数,反过来调用A类给其的注册函数
回调也可以分为同步回调和异步回调,异步回调更像是观察者模式