【进阶篇】Java 实际开发中积累的几个小技巧(二)
前言
笔者目前从事一线 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 的继承、封装和多态,与面向对象的思想一脉相承。
文章小结
作为开发技巧系列文章的第二篇,本文的内容不多但贵在实用。在之后的文章中我会分享一些关于真实项目中处理高并发、缓存的使用、异步/解耦等内容,敬请期待。
那么今天的分享到这里就暂时结束了,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!
【进阶篇】Java 实际开发中积累的几个小技巧(二)的更多相关文章
- SQL开发中容易忽视的一些小地方(二)
原文:SQL开发中容易忽视的一些小地方(二) 目的:继上一篇:SQL开发中容易忽视的一些小地方(一) 总结SQL中的null用法后,本文我将说说表联接查询. 为了说明问题,我创建了两个表,分别是学生信 ...
- Java项目开发中实现分页的三种方式一篇包会
前言 Java项目开发中经常要用到分页功能,现在普遍使用SpringBoot进行快速开发,而数据层主要整合SpringDataJPA和MyBatis两种框架,这两种框架都提供了相应的分页工具,使用 ...
- Java Web开发中路径问题小结
Java Web开发中,路径问题是个挺麻烦的问题,本文小结了几个常见的路径问题,希望能对各位读者有所帮助. (1) Web开发中路径的几个基本概念 假设在浏览器中访问了如下的页面,如图1所示: 图1 ...
- Java Web 开发中路径相关问题小结
Java Web开发中路径问题小结 (1) Web开发中路径的几个基本概念 假设在浏览器中访问了如下的页面,如图1所示: 图1 Eclipse中目录结构如图2所示: 图2 那么针对这个站点的几个基本概 ...
- 《Maven在Java项目开发中的应用》论文笔记(十七)
标题:Maven在Java项目开发中的应用 一.基本信息 时间:2019 来源:山西农业大学 关键词:Maven:Java Web:仓库:开发人员:极限编程; 二.研究内容 1.Maven 基本原理概 ...
- SQL开发中容易忽视的一些小地方( 三)
原文:SQL开发中容易忽视的一些小地方( 三) 目的:这篇文章我想说说我在工作中关于in和union all 的用法. 索引定义 : 微软的SQL SERVER提供了两种索引:聚集索引(cluster ...
- SQL开发中容易忽视的一些小地方(四)
原文:SQL开发中容易忽视的一些小地方(四) 本篇我想针对网上一些对于非聚集索引使用场合的某些说法进行一些更正. 下面引用下MSDN对于非聚集索引结构的描述. 非聚集索引结构: 1:非聚集索引与聚集索 ...
- SQL开发中容易忽视的一些小地方(五)
原文:SQL开发中容易忽视的一些小地方(五) 背景: 索引分类:众所周知,索引分为聚集索引和非聚集索引. 索引优点:加速数据查询. 问题:然而我们真的清楚索引的应用吗?你写的查询语句是否能充分应用上索 ...
- SQL开发中容易忽视的一些小地方(一)
原文:SQL开发中容易忽视的一些小地方(一) 写此系列文章缘由: 做开发三年来(B/S),发现基于web 架构的项目技术主要分两大方面: 第一:C#,它是程序的基础,也可是其它开发语言,没有开发语言也 ...
- SQL开发中容易忽视的一些小地方(六)
原文:SQL开发中容易忽视的一些小地方(六) 本文主旨:条件列上的索引对数据库delete操作的影响. 事由:今天在博客园北京俱乐部MSN群中和网友讨论了关于索引对delete的影响问题,事后感觉非常 ...
随机推荐
- GoLand打开后Project消失
一直用的好好的Goland,突然不能用了,左边栏Project"消失"了. 如下: 解决方法 遇到上面问题,一般情况是GoLand的cache问题,这个时候要将GoLand清理一下 ...
- Djiango视图层和模型层
Djiango 使用教程 目录 Djiango 使用教程 一. 视图层 1.1 HttpResponse,render,redirect 1.2 JsonResponse 1.3 form表单上传文件 ...
- dev-sidecar 让github 可以正常访问
dev-sidecar https://gitee.com/docmirror/dev-sidecar/releases
- 计算引擎-Presto
概述 参考 高质量: B站:https://mp.weixin.qq.com/s/9_lSIFSw5o8sFC8foEtA7w https://mp.weixin.qq.com/s/NmTaJjE0U ...
- 解决jenkins Git Publisher自动打tag的问题
简单配置一下 然后开始构建,然后报错如下 The recommended git tool is: NONE using credential 647ee613-5032-4894-aaeb-fe07 ...
- 一个简单的百万并发的TCP服务器的实现。
我们紧接着上篇文章,看看我们上节课的代码有什么问题? 可以明显的看出来上节课的代码公用了一个同样的缓冲区进行读写,正常的情况下我们需要封装一个结构体,让每个对应的客户端的FD都有独立的结构进行读写还有 ...
- 写了个简单爬虫,分析 Boss 直聘自动驾驶岗位
两年前,朋友想知道 Boss 直聘上关于自动驾驶的岗位有哪些 ,于是,笔者写了一个简单的爬虫 crawler-boss ,将岗位的信息收集起来. 这篇文章,笔者想分享爬虫 crawler-boss 的 ...
- Linux快速入门(一)Linux基础知识
我的环境:Ubuntu(后面涉及到的Linux均为Debian系列) Linux不同发行版的包管理方式 (1)Redhat系列:Redhat系列的包管理方式采用的是基于RPM包的YUM包管理方式 (2 ...
- 微信小程序获取手机号流程
小程序中获取手机号前提 小程序需企业认证,才可以获取用户的手机号,个人开发者是不能获取的 哔哔下 官方文档给出需先登录才可获取手机号 传送门 思路为:login登录获取code-->code传给 ...
- KingbaseES V8R3 集群运维系列 -- failover切换后集群自动恢复
案例说明: KingbaseES V8R3集群默认在触发failover切换后,为保证数据安全,原主库需要通过人工介入后,恢复为新的备库加入到集群.在无人值守的现场环境,需要在触发failover ...