【进阶篇】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的影响问题,事后感觉非常 ...
随机推荐
- PDF/Excel文件预览功能完整实现-java版本
新需求 最近接到一个新的需求,说是之前直接下载的PDF文件或者是Excel文件,现在不能直接下载,需要实现在线预览功能. 前端人员拿到这个需求后,去看了一下以前的代码,以前调用的下载接口和PDF文件预 ...
- 关于vue.js:iview-Bug-5114在iview的Poptip气泡提示内调用DatePicker出现遮挡或同时关闭窗口等冲突问题[转]
转自:https://lequ7.com/guan-yu-vuejsiviewbug5114-zai-iview-de-poptip-qi-pao-ti-shi-nei-diao-yong-datep ...
- linux系统必备软件
linux系统必备软件 需要配置好epel源 必须安装的工具 tree vim wget bash-completion bash-completion-extras lrzsz net-tools ...
- ArcMap的mxd文件没有数据、显示感叹号怎么办?
本文介绍在ArcMap软件中,导入.mxd地图文档文件后图层出现感叹号.地图显示空白等情况的解决办法. 在ArcMap软件使用过程中,我们经常会需要将包含有多个图层的.mxd地图文档文件导入软 ...
- python 写的随机抽奖小程序实现批量抽奖功能
设计思路: 1:导入EXCEL文件中,含有ID,Name两个字段的文件数据做为抽奖池 2:设置奖项,可自定义,放在.txt文件中去读取出来就可以 3:实现单轮可以抽多个名单的功能, 4:保存为.csv ...
- .NET Emit 入门教程:第六部分:IL 指令:1:概要介绍
前言: 在之前的文章中,我们完成了前面五个部分的内容学习,包括: 第一部分:Emit介绍 第二部分:构建动态程序集 第三部分:构建模块(Module) 第四部分:构建类型(Type) 第五部分:动态生 ...
- KingbaseESV8R6手工vacuum带有全局分区索引的分区表的影响
背景 客户现场有这样一个案例,有张500个分区的大表,每个分区有20万条记录.有update 非常频繁,经常会触发autovacuum.由于表很大,autovacuum 耗时很长.据现场同事反馈,手工 ...
- Kingbase ES 自定义聚合函数浅析
文章概要: 基于前面的博文<Kingbase ES 自定义聚合函数和一次改写案例>这篇文章,我们只考虑了自定义聚合函数非并行的情况, 因此,本篇文章将着重解析一下使用PLPGSQL编写并行 ...
- KingabseES 表空间限额子句(QUOTA Clause)
概述 在Oracle数据库中,DBA权限用户,可以为其他用户,创建对象,即使该用户没有任何权限.当DBA用户在该用户的表,插入数据时,提示 超出表空间的空间限额 .这就需要设置该用户的表空间的空间限额 ...
- KingbaseES Clusterware 高可用案例之---构建iSCSI共享存储
案例说明: 在KingbaseES Clusterware高可用的架构中,集群节点需要访问共享的存储设备,可以使用FC SAN.iscsi SAN.NAS等存储设备.本案例详细描述了,在Linux系统 ...