【进阶篇】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的影响问题,事后感觉非常 ...
随机推荐
- AI大模型加速RPAxAI时代到来,谁会是RPA领域的杀手级应用?
GPT等AI大模型震撼来袭,基于RPA的超级自动化仍是最佳落地载体 对话弘玑CPO贾岿,深入了解国产RPA厂商对AI大模型的探索与实践 文/王吉伟 关于RPA已死的说法,在中国RPA元年(2019年) ...
- C++入门编程----C++运算符(8)
什么是运算符 运算符是让程序执行特定的数学或逻辑操作的符号,用来表示针对数据的特定操作,也称之为操作符.C++运算符分别有算术运算符.关系运算符.逻辑运算符.赋值运算符.位运算符.移位运算符.size ...
- 【主流技术】日常工作中关于 JSON 转换的经验大全(Java)
目录 前言 一.JSON 回顾 1.1结构形式 二.其它类型 -> JSON相关 2.1 JavaBean 转 JsonObject 2.2 JavaBean 转 Json 字符串 2.3 Li ...
- [学习笔记]在Linux中使用源码编译的方式安装Nginx
准备工作 准备nginx源码包: http://nginx.org/en/download.html 准备相关的依赖包以及环境: gzip 模块需要 zlib 库 http://www.zlib. ...
- ThreadLocal父子间通信的四种解决方案
ThreadLocal父子间通信的四种解决方案 ThreadLocal 是存储在线程栈帧中的一块数据存储区域,其可以做到线程与线程之间的读写隔离. 但是在我们的日常场景中,经常会出现父线程需要向子线程 ...
- nginx proxy_set_header详解
proxy_set_header 是 Nginx 配置中的一个重要指令,特别是在使用 Nginx 作为反向代理时.该指令允许你修改由 Nginx 传递给代理后端的请求头.这对于确保后端应用程序能够接收 ...
- Windows改变终端字体后,终端一片黑 的解决办法
使用Windows终端的时候总觉得字体不好看,于是乎按照网上的教程改了一下 注册表 计算机\HKEY_CURRENT_USER\Console%SystemRoot%_system32_cmd.exe ...
- linux程序运行结果在打印到屏幕的同时写入文件
1.使用script工具 script工具是一个非常使用的工具,可以把输出到终端的信息记录下来.使用方法如: (1)输入 script log.txt命令开始保存终端输出的信息 ,其中log.txt为 ...
- ubuntu20 宽带连接
nmcli connection edit type pppoe con-name "连接名称" set pppoe.username 宽带账号 set pppoe.passwor ...
- (3)安装完python之后需要安装的Spyder集成开发环境教程
步骤一: 首先,在网站上下载你所需要的压缩文件,网址为https://files.pythonhosted.org/packages/5e/a0/ab7f29e32479d15663eab9afd1d ...