应用回调机制提升服务的代码质量
什么是回调?
类与类之间的调用最常用的是方法(函数)调用,
假如类A有2个方法m1和m2,类B有一个方法m3
如果类A在方法m1中调用类B的m3方法,如果运行在同一个线程中,其调用顺序应该是这样的:A.m1 -> B.m3;
如果m3中继续调用m2方法,A.m1->B.m3->A.m2,从类A的角度来看,则发生了回调。
回调的代码不仅仅是A的方法,也可能是A的内部类的方法,尤其是java8增加了lamda表达式的支持后,回调代码可以是一个lamda表达式。
如果B.m3与A.m2运行在同一个线程,调用过程是同步的,即同步回调。
如果B.m3与A.m2运行在不同的线程,调用过程是异步的,即异步回调。
为什么用回调?
A,B可以在不同的层级,模块间低耦合
B可以选择合适的时机回调A,非常灵活
B可以构造通用的代码处理逻辑,然后回调A,可以分离不变和易变的部分
B不需要了解A回调的那部分依赖
java中的回调
java提供了丰富的回调机制,可以通过抽象类或接口,lambda表达式构建同步回调。
public class CommonA { private static final Logger LOGGER = LoggerFactory.getLogger(CommonA.class); public static void main(String[] args) { // 1.使用抽象类回调 CommonProcess pro = new CommonProcess() { @Override protected void diff() { System.out.println(args); } }; pro.doSomething(); // 2.使用接口回调 CommonB bp = new CommonB(); bp.readAndShow("test.txt", new CommonProcessCallback() { @Override public void doSomething(String fileContent) { System.out.println(fileContent); } }); /** * 回调使用的抽象类 */ abstract static class CommonProcess { public void doSomething() { try { // 做一些检查 diff(); // 做一些通用处理 } catch (Exception ex) { LOGGER.error("", ex); } } abstract protected void diff(); } /** * 回调使用的接口 */ interface CommonProcessCallback { void doSomething(String fileContent); } // 另外一个类B,会回调A static class CommonB { // 实现读取文件内容并回调的逻辑 public void readAndShow(String fileName, CommonProcessCallback callback) { // read data from file String fileContent = getFileContent(fileName); callback.doSomething(fileContent); } }
java提供了Function,Consumer,Runnable,Supplier,Predict等可以用做lambda表达式方便的实现回调
// 3.使用java lambda 表达式 // java提供了Function,Consumer,Runnable,Supplier,Predict等可以用做lambda表达式 // 3.1 不需要参数也不需要返回值 new CommonB().readAndShow("test.txt", () -> { System.out.println("no param and result"); }); // 3.2 需要参数但是不需要返回值 new CommonB().readAndShow("test.txt", fileContent -> { System.out.println(fileContent); }); // 3.3 需要参数也需要返回值 String result = new CommonB().readAndShow("test.txt", fileContent -> { System.out.println(fileContent); return "ok"; }); System.out.println(result); // 3.4 不需要参数但是需要返回值 result = new CommonB().readAndShow("test.txt", () -> "ok"); System.out.println(result); public class CommonB { public String getFileContent(String fileName) { try { return FileUtils.readFileToString(new File(fileName), Charset.forName("utf-8")); } catch (IOException ex) { LOGGER.error("", ex); } } public void readAndShow(String fileName, Runnable callback) { // 回调Runnable的run方法,无参数,无返回值 callback.run(); } public void readAndShow(String fileName, Consumer<String> fileContentConsumer) { // 回调,有参数,无返回值 fileContentConsumer.accept(getFileContent(fileName)); } public String readAndShow(String fileName, Function<String, String> functionCallback) { // 回调,有参数也有返回值 return functionCallback.apply(getFileContent(fileName)); } public String readAndShow(String fileName, Supplier<String> supplierCallback) { // 回调,无参数,有返回值 return supplierCallback.get(); } }
其他回调例子:可以替换简单的if/else,并进一步提升代码质量
/** * boolean类型判断函数. * * @param predicate * @param trueRunner * @param falseRunner */ public static void booleanRun(Supplier<Boolean> predicate, Runnable trueRunner, Runnable... falseRunner) { Preconditions.checkNotNull(predicate); if (predicate.get()) { if (trueRunner != null) { trueRunner.run(); } } else { if (falseRunner != null) { for (Runnable runnable : falseRunner) { runnable.run(); } } } } // 不为null则把参数传入回调函数并执行 public static <T> void doIfNotNull(T obj, Consumer<T> runnable) { booleanRun(() -> obj != null, () -> runnable.accept(obj)); } // 集合不为空,则把参数传入回调函数并执行 public static <T extends Collection> void doIfNotEmpty(T obj, Consumer<T> runnable) { booleanRun(() -> CollectionUtils.isNotEmpty(obj), () -> runnable.accept(obj)); } // 字符串不为null并且不是纯空格,则把参数传入回调函数并执行 public static void doIfNotBlank(String obj, Consumer<String> runnable) { booleanRun(() -> StringUtils.isNotBlank(obj), () -> runnable.accept(obj)); } // 字符串不为空字符串则把参数传入回调函数并执行 public static void doIfNotEmpty(String obj, Consumer<String> runnable) { booleanRun(() -> StringUtils.isNotEmpty(obj), () -> runnable.accept(obj)); } // 为null则执行回调函数 public static <T> void doIfNull(T obj, Runnable runnable) { booleanRun(() -> obj == null, runnable); } // 如果回调函数返回的结果不满足条件,则重复执行一次回调函数 public static <T> T tryAgainOnCondition(Function<Boolean, T> function, Predicate<T> predicate) { T t = function.apply(false); if (predicate.test(t)) { return function.apply(true); } return t; }
使用方法:
BusinessModal bizModal = xxx; FunctionUtil.doIfNotNull(bizModal, modal -> { FunctionUtil.doIfNotNull(modal.getL1(), names::add); FunctionUtil.doIfNotNull(modal.getL2(), names::add); FunctionUtil.doIfNotNull(modal.getL3(), names::add); }); FunctionUtil.booleanRun(currentVersion::correctSummaryStatus, () -> versionMapper.updateSummaryStatusById(currentVersion)); FunctionUtil.booleanRun(() -> EntityUtil.isTableLike(currentVersion.getBizType()), () -> FunctionUtil.doIfNotEmpty(changeService.generateSql(currentVersion)), vo::setSqlDdl));
java同步回调在spring jdbc中的应用:
以查询为例,
jdbc通过QueryStatementCallback分离了connection,statement等等的资源创建、关闭、异常处理等等这些通用的处理逻辑
与不断变化的业务sql语句执行的处理逻辑,即分离了不变与易变的部分;
进一步的,通过RowCallbackHandlerResultSetExtractor 将sql执行与结果解析分离,
进一步降低了使用难度,通过这2个回调,在查询时,用户只需要编写结果处理逻辑即可方便的处理查询结果,而不需要关注connection,statement,execute等等逻辑。
// spring 中jdbc查询的封装逻辑 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; } } // 执行并调用callback return execute(new QueryStatementCallback()); } // 行回调,将jdbc查询返回的多行数据划分为每行,逐行调用callback private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<Object> { private final RowCallbackHandler rch; public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) { this.rch = rch; } @Override public Object extractData(ResultSet rs) throws SQLException { while (rs.next()) { // 逐行调用callback,即用户的行执行逻辑 this.rch.processRow(rs); } return null; } }
jdbc查询的使用方式:
JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.query("select * from table1", new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { // 按行获取名称并打印 String name = rs.getString("name"); System.out.println(name); } });
回调在java中随处可见,看了这些代码,应该有一些更深入的理解。
除了同步回调之前,java也提供了很多异步回调的机制,可以方便的用于高并发场景,异步回调在其他框架比如Spring,Guava,Netty等都有非常广泛的应用,下次介绍。
- 点赞
- 收藏
- 关注作者
评论(0)