应用回调机制提升服务的代码质量
【摘要】 使用回调可以抽取公共代码,分离稳定和易变的逻辑,有效提升系统的可维护性,提升代码质量。
什么是回调?
类与类之间的调用最常用的是方法(函数)调用,
假如类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等都有非常广泛的应用,下次介绍。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)