找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

325

积分

0

好友

45

主题
发表于 3 天前 | 查看: 9| 回复: 0

在基于 Spring Boot 框架的开发中,你是否也遇到过以下痛点:

  • 每个 Controller 里都需要使用 @Autowired 注入大量的 Service,导致代码显得冗长且重复。
  • 若想为所有 Service 方法统一添加日志记录或异常处理,则需在每个方法中手动编写,维护成本高且容易遗漏。
  • 偶尔手误将 Service 类名或方法名写错,编译时无法发现,直到运行时才报错,增加排查难度。

本文将介绍一个自研的 ServiceManager 组件,它借助 Lambda 表达式优雅地解决了上述问题。你无需再手动注入 Service,调用方法变得简洁直观,并自动集成了日志打印和异常处理。

它能解决哪些实际问题?

对比传统方式,其优势一目了然。例如,调用一个用户查询接口,传统写法如下:

// 1. 首先需要注入Service
@Autowired
private UserService userService;

// 2. 在方法中调用并处理
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(“查用户出错了“);
    }
}

这种方式混杂了注入、业务调用、日志记录和异常处理,重复代码多。

使用 ServiceManager 组件后,代码可以简化为一行:

public SerResult<UserDTO> getUser(Long userId) {
    // 一行代码完成调用:传入方法引用和参数,其余工作由组件完成
    return ServiceManager.call(UserService::queryUser, userId);
}

手动注入、日志记录、异常捕获等重复性工作全部交由组件统一处理,代码更加清晰。

组件核心逻辑

ServiceManager 的核心工作流程非常简单:

  1. 动态解析:接收你传入的 Lambda 表达式(如 UserService::queryUser),并解析出对应的 Service 实例和方法信息。
  2. 缓存优化:将解析后的元信息进行缓存,避免重复解析,提升后续调用性能。
  3. 统一执行:执行目标方法,并在此过程中统一完成日志记录和异常处理。

下面我们将分步实现这个组件,所有代码均可直接复用。

第一步:基础工具类准备

首先,需要准备几个基础的辅助类。

1. 统一响应封装类 (SerResult)

用于规范所有服务调用的返回格式。

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

import lombok.Data;

@Data
public class SerResult<T> {
    private int code; // 状态码,例如 200 成功,500 失败
    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 解析工具类 (LambdaUtil)

该工具类用于从 Lambda 表达式中提取关键的元数据信息。

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

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;

public class LambdaUtil {
    public static SerializedLambda valueOf(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 上下文工具类 (SpringUtil)

用于从 Spring 容器中动态获取 Bean 实例,这是避免手动注入的关键。

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 SpringUtil 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 {
        SpringUtil.applicationContext = applicationContext;
    }
}
4. 可序列化的函数式接口 (SerialBiFunction)

定义一个支持序列化的双参数函数接口,作为 Lambda 表达式的类型约束。

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

import java.io.Serializable;

public interface SerialBiFunction<T, U, R> extends Serializable {
    R apply(T t, U u);
}
5. 对象构建器 (InstBuilder)

提供一个链式调用的 Builder,方便创建和配置对象。

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

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

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

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

    public <V> InstBuilder<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);
    }
}

第二步:实现核心管理器 ServiceManager

这是组件的调度中心,负责协调解析、缓存和执行流程。

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.InstBuilder;
import org.pro.wwcx.ledger.common.resolver.anno.SerialBiFunction;
import org.pro.wwcx.ledger.common.util.LambdaUtil;
import org.pro.wwcx.ledger.common.util.SpringUtil;
import java.lang.invoke.SerializedLambda;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class ServiceManager {
    private static final int INIT_COUNT = 6666;
    // 缓存Lambda表达式与其元数据的映射关系
    private static final Map<SerialBiFunction<?,?,?>, LambdaMeta<?>> CACHE_LAMBDA;

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

    // 对外的统一调用入口
    @SuppressWarnings(“unchecked“)
    public static <T, U, R> SerResult<R> call(SerialBiFunction<T, U, R> fn, U param) {
        if (fn == null) {
            return SerResult.fail(“服务函数不能为空“);
        }
        // 1. 获取或解析Lambda元数据
        LambdaMeta<T> lambdaMeta = (LambdaMeta<T>) CACHE_LAMBDA.computeIfAbsent(fn, k -> {
            LambdaMeta<T> meta = parseSerialFunction(fn);
            log.debug(“已缓存Service元信息:{}“, meta.getServiceName());
            return meta;
        });
        // 2. 构建执行器
        ServiceExecutor<T, U, R> executor = InstBuilder.of(ServiceExecutor.class)
                .set(ServiceExecutor::setServiceFn, fn)
                .set(ServiceExecutor::setParam, param)
                .set(ServiceExecutor::setLambdaMeta, lambdaMeta)
                .build();
        // 3. 执行并返回结果
        return executor.callService();
    }

    // 解析Lambda表达式,获取Service类、实例和方法名
    @SuppressWarnings(“unchecked“)
    private static <T, U, R> LambdaMeta<T> parseSerialFunction(SerialBiFunction<T, U, R> fn) {
        SerializedLambda lambda = LambdaUtil.valueOf(fn);
        LambdaMeta<T> lambdaMeta = new LambdaMeta<>();
        String tClassName = lambda.getImplClass().replaceAll(“/“, “.“);
        try {
            Class<T> aClass = (Class<T>) Class.forName(tClassName);
            T inst = SpringUtil.getBean(aClass); // 关键:从Spring容器获取实例
            lambdaMeta.setClazz(aClass);
            lambdaMeta.setInst(inst);
            lambdaMeta.setServiceName(lambda.getImplMethodName());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(“未找到对应的Service类: “ + tClassName, e);
        }
        return lambdaMeta;
    }

    @lombok.Data
    private static class LambdaMeta<T> {
        private Class<T> clazz; // Service类类型
        private T inst;        // Service实例
        private String serviceName; // 方法名
    }
}

第三步:实现服务执行器 ServiceExecutor

执行器负责具体的业务方法调用,并统一处理日志和异常,这有助于保持项目架构设计的整洁性。

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.SerialBiFunction;

@Slf4j
@Setter
public class ServiceExecutor<T, U, R> {
    private SerialBiFunction<T, U, R> serviceFn;
    private U param;
    private ServiceManager.LambdaMeta<T> lambdaMeta;

    public SerResult<R> callService() {
        long startTime = System.currentTimeMillis();
        String serviceName = lambdaMeta.getClazz().getSimpleName();
        String methodName = lambdaMeta.getServiceName();
        log.info(“开始调用服务:{}#{}, 参数:{}“, serviceName, methodName, param);

        try {
            R result = serviceFn.apply(lambdaMeta.getInst(), 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());
        }
    }
}

第四步:实际应用示例

1. 定义 Service

Service 层的编写方式与平常无异。

package org.pro.wwcx.ledger.service;

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

@Service
@Slf4j
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;
    }
}
2. 在 Controller 中调用

这是变化最大的部分,Controller 变得非常简洁。

package org.pro.wwcx.ledger.controller;

import org.pro.wwcx.ledger.common.dto.SerResult;
import org.pro.wwcx.ledger.common.servicer.ServiceManager;
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 ServiceManager.call(UserService::queryUser, userId);
    }

    @PutMapping(“/{userId}“)
    public SerResult<Boolean> updateUser(
            @PathVariable Long userId,
            @RequestBody UserUpdateDTO updateDTO) {
        // 多参数方法,使用显式的Lambda表达式
        return ServiceManager.call(
                (UserService service, UserUpdateDTO dto) -> service.updateUser(userId, dto),
                updateDTO
        );
    }
}
3. 运行效果

调用成功时,控制台会输出统一格式的日志:

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

如果发生异常,执行器会捕获并返回统一的错误格式:

{
  “code“: 500,
  “msg“: “调用UserService的queryUser方法失败:用户不存在“,
  “data“: null
}

优势总结

  • 消除冗余注入:Controller 不再需要大量的 @Autowired 注解,代码更加简洁。
  • 关注点分离:日志记录、异常处理等横切关注点被统一到 ServiceExecutor 中,业务方法只需关注核心逻辑。如需修改日志策略或增强异常处理,只需改动一处。
  • 性能优化:Service 元数据被缓存,避免每次调用都进行反射解析。
  • 编译期友好:使用 Lambda 方法引用,如果方法名错误,会在编译期报错,提前发现问题。

注意事项

  • JDK 版本:需使用 JDK 8 或更高版本以支持 Lambda 表达式。
  • Service 注解:目标 Service 必须被 Spring 管理(例如使用 @Service 注解),否则 SpringUtil.getBean() 将无法获取其实例。
  • 接口多实现:如果一个接口有多个实现类,直接通过 Class 类型获取 Bean 会报错。此时需要扩展 SpringUtil,提供按 Bean 名称获取实例的方法,并在解析 Lambda 时指定具体的实现类名称。

本文介绍的 ServiceManager 组件提供了一种简化 Service 层调用的思路。你可以根据实际项目需求,在此基础上扩展更多功能,如超时控制、熔断降级等。




上一篇:Pulsar消息顺序消费实战:订阅模式对比与避坑指南
下一篇:FreeRTOS消息队列实战指南:嵌入式系统模块解耦与通信架构
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-6 23:55 , Processed in 0.069933 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表