用LLM评判器降低编程智能体评估成本:原理、设计与实践
2026/10/27 8:15:34
好的,我们来一步步手写两个非常实用的自定义注解 + 反射实现:
我们先用最原始的反射方式实现(不依赖 AOP / Spring),适合理解原理、工具类、测试框架、轻量 SDK 等场景。然后简单提一下如何升级到 Spring AOP 方式。
importjava.lang.annotation.*;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceLogExecution{Stringvalue()default"";// 可自定义日志前缀booleanlogArgs()defaulttrue;// 是否打印入参booleanlogResult()defaulttrue;// 是否打印返回值booleanlogTime()defaulttrue;// 是否打印耗时}importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.lang.reflect.Method;importjava.util.Arrays;publicclassLogExecutionUtil{privatestaticfinalLoggerlog=LoggerFactory.getLogger(LogExecutionUtil.class);/** * 包装执行目标方法,自动加日志 */publicstaticObjectinvokeWithLogging(Objecttarget,Methodmethod,Object[]args)throwsThrowable{LogExecutionanno=method.getAnnotation(LogExecution.class);if(anno==null){// 没有注解 → 直接执行returnmethod.invoke(target,args);}Stringprefix=anno.value().isEmpty()?method.getName():anno.value();StringBuilderlogPrefix=newStringBuilder("[").append(prefix).append("] ");// 打印入参if(anno.logArgs()){StringargsStr=args==null?"null":Arrays.toString(args);log.info("{}开始执行 → 参数: {}",logPrefix,argsStr);}longstart=System.nanoTime();try{Objectresult=method.invoke(target,args);// 打印返回值if(anno.logResult()){log.info("{}执行完成 → 返回: {}",logPrefix,result);}// 打印耗时if(anno.logTime()){doublems=(System.nanoTime()-start)/1_000_000.0;log.info("{}耗时: {} ms",logPrefix,String.format("%.3f",ms));}returnresult;}catch(Throwablee){log.error("{}执行异常",logPrefix,e);throwe;}}}publicclassUserService{@LogExecution(value="创建用户",logArgs=true,logResult=true,logTime=true)publicUsercreateUser(Stringusername,intage){if(age<0)thrownewIllegalArgumentException("年龄不能为负");returnnewUser(username,age);}@LogExecution("批量删除")// 使用默认值publicvoidbatchDelete(List<Long>ids){System.out.println("删除 "+ids.size()+" 条记录");}}最简单的方式:写一个代理类或在调用处手动用invokeWithLogging:
// 手动调用方式UserServiceservice=newUserService();Methodmethod=UserService.class.getMethod("createUser",String.class,int.class);LogExecutionUtil.invokeWithLogging(service,method,newObject[]{"alice",25});更常见的是结合动态代理(JDK Proxy / ByteBuddy / CGLIB)使用。
importjava.lang.annotation.*;@Repeatable(NotBlankParams.class)@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceNotBlankParam{intindex()default-1;// 参数下标(从0开始),-1表示自动推断(推荐)Stringmessage()default"参数不能为空且不能为纯空白";}@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceNotBlankParams{NotBlankParam[]value();}importjava.lang.reflect.Method;importjava.lang.reflect.Parameter;importjava.util.Objects;publicclassParamValidator{publicstaticvoidvalidateParams(Objecttarget,Methodmethod,Object[]args){Parameter[]parameters=method.getParameters();Annotation[][]paramAnnos=method.getParameterAnnotations();for(inti=0;i<parameters.length;i++){for(Annotationanno:paramAnnos[i]){if(annoinstanceofNotBlankParam){NotBlankParamnotBlank=(NotBlankParam)anno;intidx=notBlank.index()>=0?notBlank.index():i;if(idx>=args.length){thrownewIllegalArgumentException("注解 index 越界");}Objectvalue=args[idx];if(value==null||(valueinstanceofString&&((String)value).trim().isEmpty())){thrownewIllegalArgumentException(notBlank.message()+" (参数位置: "+idx+")");}}}}}}publicclassOrderService{publicvoidcreateOrder(@NotBlankParamStringorderNo,@NotBlankParam(message="收货人姓名必填")StringreceiverName,Stringremark){// remark 不校验System.out.println("创建订单:"+orderNo);}}// 在方法执行前先校验ParamValidator.validateParams(target,method,args);// 再执行方法method.invoke(target,args);| 场景 | 推荐方式 | 优点 | 实现复杂度 |
|---|---|---|---|
| 学习/理解原理 | 纯反射 + 手动代理 | 最清晰,理解最透彻 | ★★★☆☆ |
| 轻量工具类/SDK | JDK动态代理 / ByteBuddy | 无 Spring 依赖,体积小 | ★★★★☆ |
| Spring Boot 项目 | Spring AOP + @Aspect | 零侵入、最优雅 | ★★☆☆☆ |
| 高性能场景 | Annotation Processor + APT / 字节码生成 | 编译期生成代码,无反射开销 | ★★★★★ |
@Aspect@ComponentpublicclassLogExecutionAspect{@Around("@annotation(logAnno)")publicObjectaround(ProceedingJoinPointjoinPoint,LogExecutionlogAnno)throwsThrowable{// 前置日志、入参// proceed()// 后置日志、耗时、返回值}}method.getParameterAnnotations())接下来想深入哪个方向?
A. 写一个完整的JDK动态代理包装器(自动扫描 @LogExecution / @NotBlankParam)
B. 实现Spring AOP版本(AspectJ风格)
C. 做参数校验的支持分组 / 支持自定义消息国际化
D. 用Annotation Processor在编译期校验注解使用是否合法
E. 结合@Around做更复杂的场景(事务 + 日志 + 校验 一条龙)
告诉我字母,我继续带你写代码!