应用回调机制提升服务的代码质量

举报
十年磨一剑 发表于 2020/07/25 17:11:09 2020/07/25
【摘要】 使用回调可以抽取公共代码,分离稳定和易变的逻辑,有效提升系统的可维护性,提升代码质量。

什么是回调?

类与类之间的调用最常用的是方法(函数)调用,
假如类A有2个方法m1和m2,类B有一个方法m3

  1. 如果类A在方法m1中调用类B的m3方法,如果运行在同一个线程中,其调用顺序应该是这样的:A.m1 -> B.m3;

  2. 如果m3中继续调用m2方法,A.m1->B.m3->A.m2,从类A的角度来看,则发生了回调。

回调的代码不仅仅是A的方法,也可能是A的内部类的方法,尤其是java8增加了lamda表达式的支持后,回调代码可以是一个lamda表达式。

如果B.m3与A.m2运行在同一个线程,调用过程是同步的,即同步回调。

如果B.m3与A.m2运行在不同的线程,调用过程是异步的,即异步回调。

为什么用回调?

  1. A,B可以在不同的层级,模块间低耦合

  2. B可以选择合适的时机回调A,非常灵活

  3. B可以构造通用的代码处理逻辑,然后回调A,可以分离不变和易变的部分

  4. 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

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。