目录
前言
笔者现在从事一线 Java 开发今年是第 3 个年头了,从 0-1 的 SaaS、PaaS 的项目做过,基于多租户的尺度化开发项目也做过,项目的 PM 也做过...
在现实的开发中积聚了一些技巧和经验,包括线上 bug 处置惩罚、一样平常业务开发、团队开发规范等等。现在在这里分享出来,作为发展的记录和知识的更新,希望与大家共勉。
免责声明:以下全部demo、代码和测试都是出自笔者本人的构思和实践,不涉及企业隐私和商业机密,属于个人的知识积聚分享。
六、自界说注解
Spring 中的自界说注解可以灵活地定制项目开发时必要的切面 AOP 操纵,一样平常来说在接口处设置的自界说注解是使用的最多的。下面笔者以一个项目全局通用的接口请求操纵日志持久化为例子,分享一下自界说注解开发的一些小技巧。
6.1界说注解
这一步先界说出具体的注解状态和属性:- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.METHOD)
- @Inherited
- public @interface OperateLog {
- /**
- * 线索Id
- */
- String trackId() default "";
- /**
- * 具体操作行为
- */
- OperationEnum operation();
- }
复制代码 其中的具体行为操纵罗列必要提前预备好,方便后续切面内的日志操纵持久化:- @Getter
- @RequiredArgsConstructor
- public enum OperationEnum {
- XX_MODULE_ADD("xx模块","新增xx"),
- XX_MODULE_UPDATE("xx模块","修改xx");
- private final String module;
- private final String detail;
- }
复制代码 6.2切面实现
这一步是具体的切面实现,切面实现的关键在于:切面在注解声明方法的哪种次序执行,即选择 5 种通知的哪一种。
对于日志记录这种类型的,一样平常来说切面会在方法返回结果之后执行(@AfterReturning),即操纵有结果后再记录日志;而像用户登录或者接口权限校验的自界说注解,一样平常来说切面会在方法调用前(@Before)就执行。具体切面里的逻辑如下:- @Aspect
- @Component
- public class OperateLogAOP {
- @Resource
- private OperationLogService operationLogService;
- /**
- * 切面在方法返回结果之后执行,即操作有结果后再记录日志
- * @param joinPoint
- * @param operateLog
- */
- @AfterReturning(value = "@annotation(operateLog)")
- public void operateLogAopMethod(JoinPoint joinPoint, OperateLog operateLog){
- //从自定义注解中取出参数
- String trackId = operateLog.trackId();
- Assert.hasText(trackId, "trackId param error!");
- //处理参数的值,即输入的业务id值
- MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
- Object[] args = joinPoint.getArgs();
- String businessLogId = (String) AopUtils.getFieldValue(args, methodSignature, trackId);
- //操作描述
- String module = operateLog.operation().getModule();
- String detail = operateLog.operation().getDetail();
- //获取请求 http request
- HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
- //持久化入库
- OperationLog operationLog = OperationLog.builder()
- .trackId(businessLogId).module(module).detail(detail)
- .ip(IpUtil.getUserIp(request)).createTime(new Date())
- .operatorUuid(UserDataBuilder.get().getUserUuid())
- .operatorName(UserDataBuilder.get().getUserName())
- .build();
- operationLogService.save(operationLog);
- }
- }
复制代码 6.3业务使用
前面两步完成后,就到最后的业务使用了。一样平常来说日志类型的自界说注解会放在 Controller 层的接口前,具体示例如下:- /**
- * 编辑
- * @return 是否成功
- */
- @PostMapping("update")
- @OperateLog(trackId = "studyDTO.id", operation = OperationEnum.XX_MODULE_UPDATE)
- public BaseResponse<Boolean> updateStudy(@RequestBody StudyDTO studyDTO) {
- return ResultUtils.success(studyService.updateStudy(studyDTO));
- }
复制代码 七、抽象类和接口
为什么在业务设计的时间必要注意抽象类和接口的运用呢?如果只是依赖类的单一范围原则,那么业务的实现会拧成一大坨,并且代码的耦合会变紧。
抽象类非常适合多个子类共享共同特征和属性,但也兼容本身独有的行为情况,同时为子类的定制实现留出空间。
而接口则是解耦的最基本工具,接口允许将方法的界说与实在现分开,这种分离使得多个不相干的类可以或许实现同一组方法,从而包管了项目中差异部门之间的相互通信。
7.1隔离业务层与 ORM 层
- Mongo 示例
抽象类的继续关系如下:- @Service
- public class WorkerServiceImpl extends AbstractWorkerServiceImpl implements WorkerService {}
复制代码- public abstract class AbstractWorkerServiceImpl extends BaseServiceImpl<Worker, String> implements IWorkerService {}
复制代码 接口的继续关系如下:- public interface WorkerService extends IWorkerService {}
复制代码- public interface IWorkerService extends BaseService<Worker, String> {}
复制代码 底层的继续和实现:- /**
- * 以下抽象类和接口中还有自定义的一些数据库方法,与 MongoTemplate 和 MongoRepository 形成互补
- */
- public abstract class BaseServiceImpl<T, ID> implements BaseService<T, ID> {}
复制代码 - MySQL 示例
至于 MySQL 可以直接引用 mybaitisplus 的包,内里有现成的实现,都是一些数据库语句的 Java 实现。必要的情况下还可以同时引入 mybaitis 包来处置惩罚一些复杂的 sql 语句。
抽象类的继续关系如下:- @Service
- public class StudyServiceImpl extends ServiceImpl<StudyMapper, Study> implements StudyService {}
复制代码 接口的继续关系如下:- public interface StudyService extends IService<Study> {}
复制代码 底层的继续和实现:- /**
- * 以下抽象类和接口都来源于 com.baomidou.mybatisplus 包
- */
- public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {}
复制代码 7.2隔离子体系的业务实现
- facade模式
facade 称为外貌模式:为子体系中的各类(或方法)提供简洁一致的入口,隐藏子体系的复杂性。facade 层也通常充当一个中介的角色,为上层的调用者提供统一接口的同时,不直接暴露底层的实现细节。
例如在长途调用时,facade 层可以提供一个颗粒度比较粗的接口,它负责将外部请求转发给合适的服务进行处置惩罚。
service层,只关心数据,在 service 内直接注入mapper- /**
- * 只关心数据,本质上是数据库的一些操作
- */
- @Service
- public class PersonService extends ServiceImpl<PersonMapper, Person> {
- @Resource
- private PersonMapper mapper;
- //其它数据库语句
- ...
- }
复制代码 facade 层,只关心业务,在 facade内直接注入 service- /**
- * 只关心业务,不继承也不实现,被 controller 层引用
- */
- @Service
- public class PersonFacade {
- @Resource
- private PersonService service;
- //业务具体方法逻辑
- ...
- }
复制代码 上述模式的长处是将数据处置惩罚和业务处置惩罚明白地分开,业务、数据与视图层的通信靠的是 Bean 注入的方式,并不是强依赖于类的继续和接口实现,对于外部来说很好地屏蔽了具体的实现逻辑。
但是大概潜在的缺点也有:当业务简单的时间,facade 与 service 之间的边界会比较模糊,即 facade 层的存在大概是没有必要的。
7.3选择对比
如果在现实项目里的话,这两者只能选其一。
笔者对于两者在差异的项目中都使用过,实践下来的建议是:选择抽象类和接口做业务与数据的隔离。
缘故原由无它:抽象类和接口的搭配使用从本质上解释了 Java 的继续、封装和多态,与面向对象的头脑一脉相承。
文章小结
作为开发技巧系列文章的第二篇,本文的内容不多但贵在实用。在之后的文章中我会分享一些关于真实项目中处置惩罚高并发、缓存的使用、异步/解耦等内容,敬请等候。
那么今天的分享到这里就暂时竣事了,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在批评区交流!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |