复用和扩展是模板模式的主要功能,当然有另外的概念,可以起到相同的作用,就是回调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类给其的注册函数

回调也可以分为同步回调和异步回调,异步回调更像是观察者模式

发表评论

邮箱地址不会被公开。 必填项已用*标注