糖心御梦子:解锁味蕾的奇幻旅程,让甜蜜萦绕指尖
好的我们来一步步手写两个非常实用的自定义注解 反射实现LogExecution方法执行前后自动打印日志入参 返回值 耗时NotBlankParam方法参数非空/非空白校验类似 NotBlank 但作用于方法参数且用纯反射实现不依赖 Spring/Hibernate Validator我们先用最原始的反射方式实现不依赖 AOP / Spring适合理解原理、工具类、测试框架、轻量 SDK 等场景。
然后简单提一下如何升级到 Spring AOP 方式。
分LogExecution 日志注解反射实现
定义注解importjava.lang.annotation.*;Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)DocumentedpublicinterfaceLogExecution{Stringvalue()default;// 可自定义日志前缀booleanlogArgs()defaulttrue;// 是否打印入参booleanlogResult()defaulttrue;// 是否打印返回值booleanlogTime()defaulttrue;// 是否打印耗时}
反射工具类核心调用逻辑importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.lang.reflect.Method;importjava.util.Arrays;publicclassLogExecutionUtil{privatestaticfinalLoggerlogLoggerFactory.getLogger(LogExecutionUtil.class);/** * 包装执行目标方法自动加日志 */publicstaticObjectinvokeWithLogging(Objecttarget,Methodmethod,Object[]args)throwsThrowable{LogExecutionannomethod.getAnnotation(LogExecution.class);if(annonull){// 没有注解 → 直接执行returnmethod.invoke(target,args);}Stringprefixanno.value().isEmpty()?method.getName():anno.value();StringBuilderlogPrefixnewStringBuilder([).append(prefix).append(] );// 打印入参if(anno.logArgs()){StringargsStrargsnull?null:Arrays.toString(args);log.info({}开始执行 → 参数: {},logPrefix,argsStr);}longstartSystem.nanoTime();try{Objectresultmethod.invoke(target,args);// 打印返回值if(anno.logResult()){log.info({}执行完成 → 返回: {},logPrefix,result);}// 打印耗时if(anno.logTime()){doublems(System.nanoTime()-start)/1_000_
0
0;log.info({}耗时: {} ms,logPrefix,String.format(%.3f,ms));}returnresult;}catch(Throwablee){log.error({}执行异常,logPrefix,e);throwe;}}}
使用示例publicclassUserService{LogExecution(value创建用户,logArgstrue,logResulttrue,logTimetrue)publicUsercreateUser(Stringusername,intage){if(age
thrownewIllegalArgumentException(年龄不能为负);returnnewUser(username,age);}LogExecution(批量删除)// 使用默认值publicvoidbatchDelete(ListLongids){System.out.println(删除 ids.size() 条记录);}}
如何调用代理 / 手动包装最简单的方式写一个代理类或在调用处手动用invokeWithLogging// 手动调用方式UserServiceservicenewUserService();MethodmethodUserService.class.getMethod(createUser,String.class,int.class);LogExecutionUtil.invokeWithLogging(service,method,newObject[]{alice,25});更常见的是结合动态代理JDK Proxy / ByteBuddy / CGLIB使用。
分NotBlankParam 参数非空校验反射实现
定义注解可重复importjava.lang.annotation.*;Repeatable(NotBlankParams.class)Target(ElementType.PARAMETER)Retention(RetentionPolicy.RUNTIME)DocumentedpublicinterfaceNotBlankParam{intindex()default-1;// 参数下标从0开始-1表示自动推断推荐Stringmessage()default参数不能为空且不能为纯空白;}Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)publicinterfaceNotBlankParams{NotBlankParam[]value();}
校验工具类importjava.lang.reflect.Method;importjava.lang.reflect.Parameter;importjava.util.Objects;publicclassParamValidator{publicstaticvoidvalidateParams(Objecttarget,Methodmethod,Object[]args){Parameter[]parametersmethod.getParameters();Annotation[][]paramAnnosmethod.getParameterAnnotations();for(inti0;iparameters.length;i){for(Annotationanno:paramAnnos[i]){if(annoinstanceofNotBlankParam){NotBlankParamnotBlank(NotBlankParam)anno;intidxnotBlank.index()0?notBlank.index():i;if(idxargs.length){thrownewIllegalArgumentException(注解 index 越界);}Objectvalueargs[idx];if(valuenull||(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);
分升级方案生产级常用方式场景推荐方式优点实现复杂度学习/理解原理纯反射 手动代理最清晰理解最透彻★★★☆☆轻量工具类/SDKJDK动态代理 / ByteBuddy无 Spring 依赖体积小★★★★☆Spring Boot 项目Spring AOP Aspect零侵入、最优雅★★☆☆☆高性能场景Annotation Processor APT / 字节码生成编译期生成代码无反射开销★★★★★Spring AOP 快速示例LogExecutionAspectComponentpublicclassLogExecutionAspect{Around(annotation(logAnno))publicObjectaround(ProceedingJoinPointjoinPoint,LogExecutionlogAnno)throwsThrowable{// 前置日志、入参// proceed()// 后置日志、耗时、返回值}}小结 进阶方向学会了Retention(RUNTIME)反射读取注解的基本套路掌握了方法参数注解的获取方式method.getParameterAnnotations()明白了自定义注解 反射的两种主要用途日志追踪与参数/返回值校验能自己写出简易的方法切面工具不依赖框架接下来想深入哪个方向A. 写一个完整的JDK动态代理包装器自动扫描 LogExecution / NotBlankParamB. 实现Spring AOP版本AspectJ风格C. 做参数校验的支持分组 / 支持自定义消息国际化D. 用Annotation Processor在编译期校验注解使用是否合法E. 结合Around做更复杂的场景事务 日志 校验 一条龙告诉我字母我继续带你写代码
麻花传MDR国语全集-麻花传MDR国语全集应用