网易首页 > 网易号 > 正文 申请入驻

满屏 Service 注入混乱?用Lambda封装个统一调用组件,直接起飞!

0
分享至

Java精选面试题(微信小程序):5000+道面试题和选择题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计、大厂真题等,在线随时刷题!

兄弟们,咱做 Spring 项目的时候,是不是总遇到这些破事:

  • 每个Controller 里都要写@Autowired UserService userService,注入一大堆 Service,代码又乱又冗余;

  • 想统一加个日志/异常处理,得在每个 Service 方法里写一遍,改起来要疯;

  • 偶尔还会手滑把Service类名/方法名写错,编译不报错,跑起来才出问题,排查半天。

今天给大家分享个我自己写的ServiceManager组件,用Lambda搞定这些破事 —— 不用手动注入Service,调用方法像写公式一样简单,还能自动缓存、统一处理异常,新手也能秒懂秒用!

先说说这组件能解决啥实际问题?

举个栗子:以前咱调用用户查询接口,得这么写:

// 1. 先注入Service
@Autowired
private UserService userService;
// 2. 再调用方法
public SerResult 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("查用户出错了");
}
}

又是注入又是日志又是try-catch,重复代码一堆。

用了ServiceManager之后,直接写成这样:

public SerResult 

   
getUser(Long userId) {
// 一行搞定:传方法+参数,其他全帮你做
return ServiceManager.call(UserService::queryUser, userId);
}

注入?没了。日志?组件自动打。异常?组件自动处理。爽不爽?

组件核心逻辑:大白话拆解

其实这组件就干了 3 件事:

  • 你传个 Lambda(比如UserService::queryUser),它帮你找到对应的 Service 实例;

  • 把找到的实例和方法缓存起来,下次调用更快;

  • 统一执行方法,顺便把日志、异常处理都包了。

下面咱一步步来,代码都给你贴好,复制过去改改就能用。

第一步:先搭基础 —— 需要的依赖和工具类

首先得有几个小工具,不用自己写,直接复制:

1. 统一返回结果类(SerResult)

不管调用成功还是失败,都返回同一个格式,前端好处理:

package org.pro.wwcx.ledger.common.dto;
import lombok.Data;
// 服务调用的统一返回结果,前端拿到就知道是成功还是失败
@Data
publicclass SerResult {
privateint code; // 200=成功,500=失败,前端一看就懂
private String msg; // 提示信息,比如“操作成功”“查不到用户”
private T data; // 成功时返回的数据,比如用户信息
// 成功的时候调用这个方法,把数据传进去
publicstatic SerResult success(T data) {
SerResult result = new SerResult<>();
result.setCode(200);
result.setMsg("操作成功");
result.setData(data);
return result;
}
// 失败的时候调用这个方法,传错误信息
publicstatic SerResult fail(String msg) {
SerResult result = new SerResult<>();
result.setCode(500);
result.setMsg(msg);
result.setData(null);
return result;
}
}
2. Lambda 解析工具(LambdaUtil)

这工具是核心,帮咱从 Lambda 里 “扣” 出 Service 类名和方法名(不用懂原理,复制用就行):

package org.pro.wwcx.ledger.common.util;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.invoke.SerializedLambda;
// 从Lambda表达式里拿Service信息的工具
publicclass LambdaUtil {
// 传个Lambda进来,返回它对应的“元数据”(比如哪个Service,哪个方法)
public static SerializedLambda valueOf(Serializable lambda) {
if (lambda == null) {
thrownew IllegalArgumentException("Lambda不能传空!");
}
try {
// 反射拿到Lambda里的隐藏方法,不用管这行是咋回事
Method writeReplaceMethod = lambda.getClass().getDeclaredMethod("writeReplace");
writeReplaceMethod.setAccessible(true);
return (SerializedLambda) writeReplaceMethod.invoke(lambda);
} catch (Exception e) {
thrownew RuntimeException("解析Lambda出错了", e);
}
}
}
3. Spring 工具类(SpringUtil)

帮咱从 Spring 里拿 Service 实例(不用手动@Autowired就是靠它):

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;
// 从Spring里拿Bean的工具,不用自己注入Service
@Component
publicclass SpringUtil implements ApplicationContextAware {
// Spring的上下文,相当于“Bean仓库”
privatestatic ApplicationContext applicationContext;
// 从仓库里按类型拿Bean,比如拿UserService类型的实例
publicstatic T getBean(Class requiredType) {
if (applicationContext == null) {
thrownew RuntimeException("Spring还没初始化好呢!");
}
return applicationContext.getBean(requiredType);
}
// 下面这行是Spring自动调用的,不用管
@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;
// 支持序列化的双参数函数接口,Lambda要符合这个格式
public interface SerialBiFunction extends Serializable {
// 方法格式:传入T(Service实例)和U(参数),返回R(结果)
R apply(T t, U u);
}
5. 实例构建器(InstBuilder)

帮咱快速创建对象的小工具,不用写一堆set方法:

package org.pro.wwcx.ledger.common.resolver;
// 快速创建对象的工具,比如new ServiceExecutor后不用一个个set值
publicclass InstBuilder {
privatefinal T target;
// 初始化要创建的对象
private InstBuilder(Class clazz) {
try {
this.target = clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
thrownew RuntimeException("创建对象失败", e);
}
}
// 静态方法,入口:InstBuilder.of(ServiceExecutor.class)
publicstatic InstBuilder of(Class clazz) {
returnnew InstBuilder<>(clazz);
}
// 链式set值:比如.set(ServiceExecutor::setParam, param)
public InstBuilder set(Setter setter, V value) {
setter.set(target, value);
returnthis;
}
// 最后调用build()拿到对象
public T build() {
return target;
}
// 定义setter的格式
@FunctionalInterface
publicinterface Setter {
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
publicclass ServiceManager {
// 缓存初始化大小,6666够咱用了,不够再改
privatestaticfinalint INIT_COUNT = 6666;
// 缓存Lambda对应的Service信息,key是Lambda,value是Service元数据
privatestaticfinal Map , LambdaMeta > CACHE_LAMBDA;
// 静态代码块,项目启动时就初始化缓存
static {
CACHE_LAMBDA = new ConcurrentHashMap<>(INIT_COUNT);
}
// 对外提供的调用方法:传Lambda(比如UserService::queryUser)和参数,返回结果
@SuppressWarnings("unchecked")
publicstatic SerResult call(SerialBiFunction fn, U param) {
// 先检查:Lambda不能传空
if (fn == null) {
return SerResult.fail("服务函数不能为空!");
}
// 1. 从缓存拿Service信息:有就直接用,没有就解析并缓存
LambdaMeta lambdaMeta = (LambdaMeta ) CACHE_LAMBDA.computeIfAbsent(fn, k-> {
// 解析Lambda,拿到Service实例、类名这些信息
LambdaMeta meta = parseSerialFunction(fn);
log.debug("缓存Service信息:{}", meta.getServiceName());
return meta;
});
// 2. 创建执行器,把Lambda、参数、Service信息传进去
ServiceExecutor executor = InstBuilder.of(ServiceExecutor .class)
.set(ServiceExecutor::setServiceFn, fn) // 传Lambda方法
.set(ServiceExecutor::setParam, param) // 传参数
.set(ServiceExecutor::setLambdaMeta, lambdaMeta) // 传Service信息
.build(); // 构建执行器
// 3. 执行方法,返回结果
return executor.callService();
}
// 解析Lambda:从Lambda里拿到Service类名、实例、方法名
@SuppressWarnings("unchecked")
privatestatic LambdaMeta parseSerialFunction(SerialBiFunction fn) {
// 用LambdaUtil拿到Lambda的元数据
SerializedLambda lambda = LambdaUtil.valueOf(fn);
// 封装Service信息的对象
LambdaMeta lambdaMeta = new LambdaMeta<>();
// 1. 解析Service类名:Lambda里的类名是“com/example/UserService”,要改成“com.example.UserService”
String tClassName = lambda.getImplClass().replaceAll("/", ".");
try {
// 2. 拿到Service的Class对象(比如UserService.class)
Class aClass = (Class ) Class.forName(tClassName);
// 3. 从Spring里拿Service实例(不用@Autowired就是靠这行)
T inst = SpringUtil.getBean(aClass);
// 4. 把信息存到lambdaMeta里
lambdaMeta.setClazz(aClass); // 存Service的Class
lambdaMeta.setInst(inst); // 存Service实例
lambdaMeta.setServiceName(lambda.getImplMethodName()); // 存方法名(比如queryUser)
} catch (ClassNotFoundException e) {
// 找不到类就抛异常
thrownew RuntimeException("没找到Service类:" + tClassName, e);
}
return lambdaMeta;
}
// 封装Service信息的内部类:存Class、实例、方法名
@lombok.Data
privatestaticclass LambdaMeta {
private Class clazz; // Service的Class(比如UserService.class)
private T inst; // Service实例(Spring里的Bean)
private String serviceName; // 方法名(比如queryUser)
}
}
第三步:执行器 ——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;
// 执行Service方法的类,统一打日志、处理异常
@Slf4j
@Setter
publicclass ServiceExecutor {
private SerialBiFunction serviceFn; // 要执行的Lambda方法
private U param; // 方法参数
private ServiceManager.LambdaMeta lambdaMeta; // Service信息
// 执行方法的核心逻辑
public SerResult callService() {
// 记录开始时间,方便算耗时
long startTime = System.currentTimeMillis();
String serviceName = lambdaMeta.getClazz().getSimpleName(); // 比如UserService
String methodName = lambdaMeta.getServiceName(); // 比如queryUser
log.info("开始调用:{}的{}方法,参数:{}", serviceName, methodName, param);
try {
// 真正执行方法:用Service实例调用Lambda方法
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());
}
}
}
第四步:怎么用?举个实际例子

咱以用户查询和更新为例,看 Controller 里怎么写:

1. 先写个 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;
// 正常的Service,该咋写咋写
@Service
publicclass UserService {
// 查用户:根据ID查
public UserDTO queryUser(Long userId) {
// 这里模拟查数据库,实际项目里换JDBC/MyBatis
UserDTO user = new UserDTO();
user.setUserId(userId);
user.setUserName("张三");
user.setAge(25);
return user;
}
// 更新用户:传ID和更新参数
public Boolean updateUser(Long userId, UserUpdateDTO updateDTO) {
// 这里模拟更新数据库
log.info("更新用户{}的信息:{}", userId, updateDTO);
returntrue; // 返回更新成功
}
}
2. 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")
publicclass UserController {
// 查用户:不用注入UserService!一行搞定
@GetMapping("/{userId}")
public SerResult getUser(@PathVariable Long userId) {
// 直接传Lambda(UserService::queryUser)和参数(userId)
return ServiceManager.call(UserService::queryUser, userId);
}
// 更新用户:同样不用注入
@PutMapping("/{userId}")
public SerResult updateUser(
@PathVariable Long userId,
@RequestBody UserUpdateDTO updateDTO) {
// 这里要注意:因为updateUser有两个参数,所以要显式指定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)

要是出错了,比如传个不存在的用户 ID(假设数据库查不到会抛异常),日志会打错误信息,返回给前端的结果是:

{
"code": 500,
"msg": "调用UserService的queryUser方法失败:用户不存在",
"data": null
}
这组件的好处:总结一下
  • 不用再写 @Autowired:Controller 里干干净净,再也不用注入一堆 Service;

  • 统一日志 / 异常:想改日志格式、加权限校验,只需要改ServiceExecutor,不用改每个方法;

  • 缓存优化:解析过的 Service 信息会缓存,下次调用更快;

  • 类型安全:写 Lambda 的时候,方法名错了编译就报错,不用等到运行才发现。

注意事项:避坑指南
  • JDK 版本:用JDK8及以上,Lambda 表达式是 JDK8 才有的;

  • Service 要加@Service:Spring才能扫描到,不然SpringUtil拿不到实例;

  • 多实现类的情况:如果一个接口有多个实现(比如UserServiceUserServiceImpl1UserServiceImpl2),需要在SpringUtil里加按名称拿 Bean 的方法。

来源:https://juejin.cn/post/7575017581036765222

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

最近有很多人问,有没有读者或者摸鱼交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!

文章有帮助的话,点在看,转发吧!

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
国防大学教授:“正义使命-2025”演习 “开局即打”

国防大学教授:“正义使命-2025”演习 “开局即打”

环球网资讯
2025-12-29 10:29:22
英达多次公开喊话惹争议,网友:巴图原不原谅,宋丹丹说了算

英达多次公开喊话惹争议,网友:巴图原不原谅,宋丹丹说了算

丁丁鲤史纪
2025-12-31 09:21:53
1号线逆天改命,把不可能变成了可能!!——上海地铁1号线探秘

1号线逆天改命,把不可能变成了可能!!——上海地铁1号线探秘

历史小胡
2026-01-01 17:41:51
不管有没有钱,也不要选择这3种养老方式,那不是养老,是在等死

不管有没有钱,也不要选择这3种养老方式,那不是养老,是在等死

蝉吟槐蕊
2025-12-29 17:37:10
破案!世预打不明白的小曾,一回CBA就暴走原因找到,胡队要背锅

破案!世预打不明白的小曾,一回CBA就暴走原因找到,胡队要背锅

后仰大风车
2026-01-02 09:10:06
任职少林寺住持刚5个月,释印乐再迎喜讯,释永信肠子悔青也晚了

任职少林寺住持刚5个月,释印乐再迎喜讯,释永信肠子悔青也晚了

天启大世界
2026-01-01 22:48:19
何穗元旦晒儿子!和陈伟霆陪娃迎新年,一个月Winsome趴爸爸背上

何穗元旦晒儿子!和陈伟霆陪娃迎新年,一个月Winsome趴爸爸背上

乐悠悠娱乐
2026-01-02 10:31:08
海警突发激烈对峙!为拦截美军火船入台,解放军做好随时开火准备

海警突发激烈对峙!为拦截美军火船入台,解放军做好随时开火准备

荐史
2025-12-30 22:12:12
伊朗,突发!崩盘、失控!发生了什么?

伊朗,突发!崩盘、失控!发生了什么?

证券时报
2025-12-31 08:07:06
过去5场3次40+,豪取6连胜!伦纳德狂轰45分7板,末节独得20分

过去5场3次40+,豪取6连胜!伦纳德狂轰45分7板,末节独得20分

无术不学
2026-01-02 14:19:48
深夜利好!9个军工龙头回购股票,这2个龙头回购超1亿元

深夜利好!9个军工龙头回购股票,这2个龙头回购超1亿元

风风顺
2026-01-02 04:10:03
记者:塞蒂恩下课后国安曾接触邵佳一;西海岸当时已在谈续约

记者:塞蒂恩下课后国安曾接触邵佳一;西海岸当时已在谈续约

懂球帝
2026-01-02 11:36:12
震碎你的三观!鸦片战争以来,满洲贵族是如何防范汉民的?

震碎你的三观!鸦片战争以来,满洲贵族是如何防范汉民的?

历史按察使司
2025-12-29 10:38:17
Here we go!罗马诺:布伦南-约翰逊3350万镑加盟水晶宫

Here we go!罗马诺:布伦南-约翰逊3350万镑加盟水晶宫

懂球帝
2026-01-02 02:33:17
中国蓝箭航天,挑战马斯克

中国蓝箭航天,挑战马斯克

陆弃
2026-01-01 08:10:03
泰国政坛惊天逆转!多地政治家族集体“倒戈”,选战格局大洗牌!

泰国政坛惊天逆转!多地政治家族集体“倒戈”,选战格局大洗牌!

万国明信片
2026-01-02 13:50:31
出大事了!局势升级!特朗普说到做到!一觉醒来,美军打响复仇战

出大事了!局势升级!特朗普说到做到!一觉醒来,美军打响复仇战

爱吃醋的猫咪
2025-12-31 20:25:28
领教了三个神医后发现:我们这届人不配拥有中医

领教了三个神医后发现:我们这届人不配拥有中医

田先生研究室
2025-12-15 22:15:25
队报:加蓬体育部长宣布暂停国家队一切活动,开除奥巴梅扬

队报:加蓬体育部长宣布暂停国家队一切活动,开除奥巴梅扬

懂球帝
2026-01-01 19:20:12
中国火箭,凭什么用十年追上 SpaceX?

中国火箭,凭什么用十年追上 SpaceX?

Thurman在昆明
2026-01-02 12:57:40
2026-01-02 15:40:49
Java精选
Java精选
一场永远也演不完的戏
1766文章数 3859关注度
往期回顾 全部

科技要闻

新势力年榜:零跑险胜华为,蔚来小鹏新高

头条要闻

央视独家披露福建舰罕见画面:瞬间弹射"零帧"急停

头条要闻

央视独家披露福建舰罕见画面:瞬间弹射"零帧"急停

体育要闻

英超离谱夜?4战全平3场0-0 曼城红军翻车

娱乐要闻

武林外传开播20年,郭芙蓉打工期结束

财经要闻

8200亿扩产潮下的锂电供应链之战

汽车要闻

奇瑞汽车12月销量超23万辆 全年超263万辆

态度原创

家居
亲子
艺术
公开课
军事航空

家居要闻

无形有行 自然与灵感诗意

亲子要闻

新年快乐,跨年的仪式感拍新年第一张合影

艺术要闻

雷蒙多·德·马德拉索:定义“美丽时代”的肖像大师

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

泽连斯基:乌全力推动恢复战俘交换工作

无障碍浏览 进入关怀版