自研小工具:用Lambda换一种Service调用写法(纯个人技术尝试,仅供参考)

9,455 阅读4分钟

前言

日常写Spring项目一直都是标准注入+手动try-catch打日志的写法,最近闲着想折腾点不一样的编码风格,于是自己基于Lambda封装了一套轻量调用组件。 纯粹是个人学习、尝试新写法写出来的小东西,没有任何推广、强制使用的意思,觉得好用可以拿去改改自用,不喜欢直接划走就行,每个人编码习惯不同,不必争论好坏。

新旧写法简单对比

传统标准写法

@Autowired
private UserService userService;

public SerResult<UserDTO> getUser(Long userId) {
    try {
        log.info("开始查用户,ID:{}", userId);
        UserDTO user = userService.queryUser(userId);
        log.info("查询成功,结果:{}", user);
        return SerResult.success(user);
    } catch (Exception e) {
        log.error("查询失败", e);
        return SerResult.fail("查用户出错了");
    }
}

自研组件新式写法

public SerResult<UserDTO> getUser(Long userId) {
    return ServiceInvoker.call(UserService::queryUser, userId);
}

把日志、异常捕获、Bean获取统一封装,Controller只保留核心业务调用一行代码,只是我个人偏好的简洁风格。

组件实现思路

整体实现逻辑很简单,仅做技术思路分享:

  1. 通过序列化Lambda反射解析目标Service类与对应方法;
  2. 借助Spring上下文工具动态获取容器Bean,省去@Autowired;
  3. 增加本地缓存,避免重复反射解析;
  4. 统一封装执行逻辑,内置日志打印、异常捕获、耗时统计。

完整代码实现

包名按需自行修改,全部代码为自己编写,无第三方复杂依赖

1. 统一返回体 SerResult

package org.pro.wwcx.ledger.common.dto;
import lombok.Data;

@Data
public class SerResult<T> {
    private int code;
    private String msg;
    private T data;

    public static <T> SerResult<T> success(T data) {
        SerResult<T> result = new SerResult<>();
        result.setCode(200);
        result.setMsg("操作成功");
        result.setData(data);
        return result;
    }

    public static <T> SerResult<T> fail(String msg) {
        SerResult<T> result = new SerResult<>();
        result.setCode(500);
        result.setMsg(msg);
        result.setData(null);
        return result;
    }
}

2. Lambda解析工具 LambdaParseUtil

package org.pro.wwcx.ledger.common.util;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.invoke.SerializedLambda;

public class LambdaParseUtil {
    public static SerializedLambda getSerializedLambda(Serializable lambda) {
        if (lambda == null) {
            throw new IllegalArgumentException("Lambda不能传空!");
        }
        try {
            Method writeReplaceMethod = lambda.getClass().getDeclaredMethod("writeReplace");
            writeReplaceMethod.setAccessible(true);
            return (SerializedLambda) writeReplaceMethod.invoke(lambda);
        } catch (Exception e) {
            throw new RuntimeException("解析Lambda出错", e);
        }
    }
}

3. Spring容器工具 SpringBeanUtil

package org.pro.wwcx.ledger.common.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringBeanUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    public static <T> T getBean(Class<T> requiredType) {
        if (applicationContext == null) {
            throw new RuntimeException("Spring上下文未初始化");
        }
        return applicationContext.getBean(requiredType);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringBeanUtil.applicationContext = applicationContext;
    }
}

4. 序列化函数接口 SerialBiFunc

package org.pro.wwcx.ledger.common.resolver.anno;
import java.io.Serializable;

@FunctionalInterface
public interface SerialBiFunc<T, U, R> extends Serializable {
    R apply(T t, U u);
}

5. 通用对象构建器 ObjectBuilder

package org.pro.wwcx.ledger.common.resolver;

public class ObjectBuilder<T> {
    private final T target;

    private ObjectBuilder(Class<T> clazz) {
        try {
            this.target = clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException("创建对象失败", e);
        }
    }

    public static <T> ObjectBuilder<T> of(Class<T> clazz) {
        return new ObjectBuilder<>(clazz);
    }

    public <V> ObjectBuilder<T> set(Setter<T, V> setter, V value) {
        setter.set(target, value);
        return this;
    }

    public T build() {
        return target;
    }

    @FunctionalInterface
    public interface Setter<T, V> {
        void set(T target, V value);
    }
}

6. 核心调用入口 ServiceInvoker

package org.pro.wwcx.ledger.common.servicer;
import lombok.extern.slf4j.Slf4j;
import org.pro.wwcx.ledger.common.dto.SerResult;
import org.pro.wwcx.ledger.common.resolver.ObjectBuilder;
import org.pro.wwcx.ledger.common.resolver.anno.SerialBiFunc;
import org.pro.wwcx.ledger.common.util.LambdaParseUtil;
import org.pro.wwcx.ledger.common.util.SpringBeanUtil;
import java.lang.invoke.SerializedLambda;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class ServiceInvoker {
    private static final int INIT_COUNT = 6666;
    private static final Map<SerialBiFunc<?,?,?>, ServiceLambdaMeta<?>> LAMBDA_CACHE;

    static {
        LAMBDA_CACHE = new ConcurrentHashMap<>(INIT_COUNT);
    }

    @SuppressWarnings("unchecked")
    public static <T,U,R> SerResult<R> call(SerialBiFunc<T,U,R> fn, U param){
        if (fn == null) {
            return SerResult.fail("服务函数不能为空!");
        }
        ServiceLambdaMeta<T> lambdaMeta = (ServiceLambdaMeta<T>) LAMBDA_CACHE.computeIfAbsent(fn, k-> {
            ServiceLambdaMeta<T> meta = parseLambdaFunc(fn);
            log.debug("缓存Service方法元数据:{}#{}", meta.clazz.getSimpleName(), meta.methodName);
            return meta;
        });

        ServiceExecuteHandler<T,U,R> executor = ObjectBuilder.of(ServiceExecuteHandler.class)
                .set(ServiceExecuteHandler::setFunc, fn)
                .set(ServiceExecuteHandler::setParam, param)
                .set(ServiceExecuteHandler::setLambdaMeta, lambdaMeta)
                .build();
        return executor.execute();
    }

    @SuppressWarnings("unchecked")
    private static <T, U, R> ServiceLambdaMeta<T> parseLambdaFunc(SerialBiFunc<T,U,R> fn) {
        SerializedLambda lambda = LambdaParseUtil.getSerializedLambda(fn);
        String tClassName = lambda.getImplClass().replaceAll("/", ".");
        try {
            Class<T> aClass = (Class<T>) Class.forName(tClassName);
            T inst = SpringBeanUtil.getBean(aClass);
            ServiceLambdaMeta<T> meta = new ServiceLambdaMeta<>();
            meta.clazz = aClass;
            meta.serviceBean = inst;
            meta.methodName = lambda.getImplMethodName();
            return meta;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("未找到对应Service类:" + tClassName, e);
        }
    }

    @lombok.Data
    private static class ServiceLambdaMeta<T> {
        private Class<T> clazz;
        private T serviceBean;
        private String methodName;
    }
}

7. 执行处理器 ServiceExecuteHandler

package org.pro.wwcx.ledger.common.servicer;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.pro.wwcx.ledger.common.dto.SerResult;
import org.pro.wwcx.ledger.common.resolver.anno.SerialBiFunc;

@Slf4j
@Setter
public class ServiceExecuteHandler<T, U, R> {
    private SerialBiFunc<T, U, R> func;
    private U param;
    private ServiceInvoker.ServiceLambdaMeta<T> lambdaMeta;

    public SerResult<R> execute() {
        long startTime = System.currentTimeMillis();
        String serviceName = lambdaMeta.getClazz().getSimpleName();
        String methodName = lambdaMeta.getMethodName();
        log.info("开始调用:{}.{},参数:{}", serviceName, methodName, param);
        try {
            R result = func.apply(lambdaMeta.getServiceBean(), param);
            long costTime = System.currentTimeMillis() - startTime;
            log.info("调用成功:{}.{},耗时{}ms,结果:{}", serviceName, methodName, costTime, result);
            return SerResult.success(result);
        } catch (Exception e) {
            long costTime = System.currentTimeMillis() - startTime;
            log.error("调用失败:{}.{},耗时{}ms", serviceName, methodName, costTime, e);
            return SerResult.fail(serviceName + "#" + methodName + "执行失败:" + e.getMessage());
        }
    }
}

使用示例

业务Service(无需改动,正常加@Service)

package org.pro.wwcx.ledger.service;
import lombok.extern.slf4j.Slf4j;
import org.pro.wwcx.ledger.dto.UserDTO;
import org.pro.wwcx.ledger.dto.UserUpdateDTO;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class UserService {
    public UserDTO queryUser(Long userId) {
        UserDTO user = new UserDTO();
        user.setUserId(userId);
        user.setUserName("张三");
        user.setAge(25);
        return user;
    }

    public Boolean updateUser(Long userId, UserUpdateDTO updateDTO) {
        log.info("更新用户{}信息:{}", userId, updateDTO);
        return true;
    }
}

Controller调用示例

package org.pro.wwcx.ledger.controller;
import org.pro.wwcx.ledger.common.dto.SerResult;
import org.pro.wwcx.ledger.common.servicer.ServiceInvoker;
import org.pro.wwcx.ledger.dto.UserDTO;
import org.pro.wwcx.ledger.dto.UserUpdateDTO;
import org.pro.wwcx.ledger.service.UserService;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/{userId}")
    public SerResult<UserDTO> getUser(@PathVariable Long userId) {
        return ServiceInvoker.call(UserService::queryUser, userId);
    }

    @PutMapping("/{userId}")
    public SerResult<Boolean> updateUser(
            @PathVariable Long userId,
            @RequestBody UserUpdateDTO updateDTO) {
        return ServiceInvoker.call(
                (UserService service, UserUpdateDTO dto) -> service.updateUser(userId, dto),
                updateDTO
        );
    }
}

运行日志效果

正常调用输出:

开始调用:UserService.queryUser,参数:1001
调用成功:UserService.queryUser,耗时5ms,结果:UserDTO(userId=1001, userName=张三, age=25)

异常时自动捕获并返回标准格式:

{
  "code": 500,
  "msg": "UserService#queryUser执行失败:用户不存在",
  "data": null
}

简单说明&注意事项

  1. 仅个人技术尝试,单纯想换一种编码写法,适合感兴趣的朋友参考学习,不推荐强行接入生产;
  2. 依赖JDK8及以上Lambda序列化特性;
  3. 所有业务Service必须标注@Service,才能被Spring容器扫描;
  4. 接口存在多个实现类的场景需要自行扩展按BeanName获取逻辑,当前版本未适配;
  5. 单参数方法可直接方法引用,多参数需要手动补全Lambda表达式。

写在最后

这套组件完全是自己闲暇时折腾出来的小工具,只是单纯分享学习过程,每个人编码习惯、项目架构都不一样,适合自己的写法才是最好的。 不喜勿喷,划走即可,感谢理解。