整体回调机制流程如下图所示:
1.2 回调的结构
1.3 回调的作用
2.回调的实现方式
2.1 通过接口实现回调
java可以通过定义一个回调接口,包含需要回调的方法,然后在业务逻辑中通过传递接口的实现类,触发回调。示例如下:
// 1. 定义回调接口
interface Callback {
void onComplete(String result);
}
// 2. 定义业务逻辑类,接受回调
class Task {
public void execute(Callback callback) {
// 模拟一些业务逻辑处理
String result = "Task Completed!";
// 回调通知调用方
callback.onComplete(result);
}
}
// 3. 使用回调
public class CallBackTest {
public static void main(String[] args) {
Task task = new Task();
// 通过匿名类实现回调
task.execute(new Callback() {
@Override
public void onComplete(String result) {
System.out.println("Callback received: " + result);
}
});
}
}
2.2 通过Lambda表达式实现回调(Java 8+)
2.3 异步回调
import java.util.concurrent.CompletableFuture;
interface Callback {
void onComplete(String result);
}
class AsyncTask {
public void executeAsync(Callback callback) {
CompletableFuture.supplyAsync(() -> {
// 模拟耗时任务
try { Thread.sleep(2000); } catch (InterruptedException e) { }
return "Task Completed!";
}).thenAccept(callback::onComplete); // 任务完成后回调
}
}
public class CallBackTest {
public static void main(String[] args) {
AsyncTask task = new AsyncTask();
// 异步回调,任务完成后调用
task.executeAsync(result -> System.out.println("Async callback received: " + result));
System.out.println("Main thread continues executing...");
}
}
2.4 支付异步回调
@Controller
@RequestMapping("/api/mall/pay")
@Api("支付相关接口")
@Slf4j
public class PayController {
@Resource
private PayService payService;
@Resource
private WxPayConfig wxPayConfig;
@GetMapping(value="/create", produces = "text/html")
@ApiOperation("发起支付")
public ModelAndView create(@RequestParam("orderNo") String orderNo,
@RequestParam("payType") Integer payType) {
// 发起支付,会传递回调通知接口地址
PayResponse response = payService.create(orderNo, payType);
// 支付方式不同,渲染就不同, WXPAY_NATIVE使用codeUrl, ALIPAY_PC使用body
Map<String, String> map = new HashMap<>();
if (Objects.equals(payType, PayConstant.TYPE_WXPAY)) {
map.put("codeUrl", response.getCodeUrl());
map.put("orderNo", orderNo);
map.put("returnUrl", wxPayConfig.getReturnUrl());
return new ModelAndView("wxView", map);
}else if (Objects.equals(payType, PayConstant.TYPE_ALIPAY)) {
map.put("body", response.getBody());
return new ModelAndView("alipayView", map);
}
throw new RuntimeException("暂不支持的支付类型");
}
@PostMapping("/notify")
@ApiOperation("异步通知")
@ResponseBody
public String asyncNotify(@RequestBody String notifyData) {
return payService.asyncNotify(notifyData);
}
}
配置文件:
可以看到在发起支付的时候把回调的通知接口地址给到支付平台,支付平台通过回调通知接口告知调用方支付成功与否,这种方式也是回调思想的体现。
3.回调机制在主流框架中的应用
跟随代码进入到实现类AbstractBeanFactory
,实现逻辑代码如下:
#doGetBean()
就是获取bean的核心逻辑所在(ps: Spring框架里面do开头的都是真正实现逻辑的地方),下面是单例bean的创建核心逻辑:
注意,注意,注意,这里的#getSingleton()
方法调用使用了函数式参数传递,典型的函数式编程,这就意味着后续会来执行回调方法#createBean()
。
接下来看看#getSingleton()
:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
// 对单例缓存对象map加锁
synchronized (this.singletonObjects) {
// 从缓存中获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
.....
// 单例bean创建前置处理,标记当前bean在创建中
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 初始化 bean
// 这个过程其实是调用 createBean() 方法
// singletonFactory由回调方法产生
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
.....
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// 单例bean创建后置处理,标记当前bean不在创建中
afterSingletonCreation(beanName);
}
// 加入缓存
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
你会发现该方法的第二个参数ObjectFactory
是一个函数式接口,执行到代码singletonObject = singletonFactory.getObject()
的时候会回调执行#createBean()
来创建bean对象。
这就是Spring框架bean工厂容器核心逻辑生成bean的写法,可见回调和函数式编程的实用性。
4.回调机制在业务系统开发中实战
做过金融行业的大概都知道,这么多银行和金融平台的信贷数据格式都是不一样的,这时候加入有一个系统需要对接各个银行或者金融平台的数据就很麻烦了,总不能一家银行一家银行地去硬编码实现吧!!!
数据对接的流程大体是固定的:通过调银行接口或者访问银行数据文件获取源数据,然后进行清洗转换成业务系统需要的格式数据,导入业务系统,记录日志。
这里变换的就是数据转换,我们将它抽取出来通过函数式编程进行封装,然后使用 Lambda 来简化代码使用,从而完美应对不同银行的数据转换,做到灵活不失优雅,下面是代码封装和简单示例:
// 定义转换数据的函数式接口
@FunctionalInterface
public interface DataTransformer<T, R> {
R transform(T input);
}
// 逻辑处理类
public class DataProcessor<T, R> {
public R processData(T data, DataTransformer<T, R> transformer) {
return transformer.transform(data);
}
}
// 使用场景
public class DataProcessor<T, R> {
public static <T, R> R processData(T data, DataTransformer<T, R> transformer) {
return transformer.transform(data);
}
public static void main(String[] args) {
// 简单示例1
Integer length = DataProcessor.processData("Hello", String::length);
System.out.println(length); // 输出 5
// 示例2:从person转换为student
Person person = new Person();
person.setName("zhang san");
person.setAge(18);
Student student = DataProcessor.processData(person, DataProcessor::convertToStudent);
System.out.println(student);
}
private static Student convertToStudent(Person person) {
Student student = new Student();
student.setName(person.getName());
student.setAge(person.getAge());
return student;
}
}
5.总结
最后说一句(求关注!别白嫖!)
如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。
关注公众号:woniuxgg,在公众号中回复:笔记 就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!