【进阶篇】Java 实际开发中积累的几个小技巧(二)

这篇具有很好参考价值的文章主要介绍了【进阶篇】Java 实际开发中积累的几个小技巧(二)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录
  • 前言
  • 六、自定义注解
    • 6.1定义注解
    • 6.2切面实现
    • 6.3业务使用
  • 七、抽象类和接口
    • 7.1隔离业务层与 ORM 层
    • 7.2隔离子系统的业务实现
    • 7.3选择对比
  • 文章小结

前言

笔者目前从事一线 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 的继承、封装和多态,与面向对象的思想一脉相承。


文章小结

作为开发技巧系列文章的第二篇,本文的内容不多但贵在实用。在之后的文章中我会分享一些关于真实项目中处理高并发、缓存的使用、异步/解耦等内容,敬请期待。

那么今天的分享到这里就暂时结束了,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!文章来源地址https://www.toymoban.com/news/detail-853257.html

到了这里,关于【进阶篇】Java 实际开发中积累的几个小技巧(二)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • VSCode 中使用 AI智能编程工具的几个小妙招

    可能你已经在IDE中安装了CodeGeeX,也了解到CodeGeeX能够帮助你编写代码、调试问题、创建文档,生成单元测试等。 但是总有些“Wow!”时刻,还在等你发现。今天就介绍几个CodeGeeX插件在VSCode中的使用技巧和小窍门。 一、侧边栏放右边,效率倍增 默认情况下,CodeGeeX插件在V

    2024年02月05日
    浏览(35)
  • mac环境使用pkgbuild命令打pkg包的几个小细节

    最近,研发提出要使用jenkins来自动生成mac环境下的pkg包,研究了一下,可以使用pkgbuild来打包。但是有几个小细节需要注意一下: 目前还没研究好,后续补充。 记之。

    2024年02月09日
    浏览(24)
  • 【java】日常开发中提升技术的几个建议

    日常开发中,都是在做业务需求,如何提升自己的技术呢? 所以,本文作者整理了提升技术的13个建议,小伙伴们,一起加油。 比如,对于Java程序员来说,要了解Java语言的基本概念和核心特性,包括面向对象编程、集合框架、异常处理、多线程等等。可以通过阅读Java的官方

    2024年02月05日
    浏览(39)
  • 你不知道的几个JavaScript 高阶技巧

    基础: 高阶: 基础: 高阶: 基础: 高阶: 基础: 高阶: 基础: 高阶: 基础: 高阶: 基础: 高阶: 基础 高阶 更简单的方法: null 是一个  value ,然而 undefined 不是. null 像一个空盒子,但 undefined 不是. 传递 null 时, 不采用 默认值。然而,当未定义或未传递任何内容时,

    2024年02月08日
    浏览(36)
  • 企业级项目开发中保证接口安全的11个小技巧,详细案例指导

    企业级项目开发中保证接口安全的11个小技巧,详细案例指导。 如何保证接口的安全性? 1 参数校验 保证接口安全的第一步,也是最重要的一步,需要对接口的请求参数做校验。 如果我们把接口请求参数的校验做好了,真的可以拦截大部分的无效请求。 我们可以按如下步骤

    2024年01月23日
    浏览(33)
  • Java中ElasticSearch的几个简单查询

    1 term查询 单值查询,代表完全匹配,类似于mysql中的 = 。在搜索前不会对进行分词,直接去文档分词库匹配。筛选出一个字段等于特定值的所有记录。 会对查询结果进行打分(score),分值越高的数据排名越靠前。 在SQL中:  ES中的查询有很大差别,它把sql中的一条数

    2024年02月06日
    浏览(26)
  • Java避免死锁的几个常见方法(有测试代码和分析过程)

    目录 Java避免死锁的几个常见方法 死锁产生的条件 上死锁代码 然后 :jstack 14320 jstack.text Java避免死锁的几个常见方法 Java避免死锁的几个常见方法 避免一个线程同时获取多个锁。 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。 尝试使用定时锁,使

    2023年04月16日
    浏览(67)
  • Armbian折腾记5(青龙Tools搭建、分享几个小工具)

    2.安装docker容器(端口可以更改,默认15000) 这里顺便补充一点删除docker容器的步骤 安装好了后浏览器输入   IP:15000   即可 3.这里才是我想分享的, 青龙tools的配置 。。。我搜了半天没怎么搜到清晰具体的,自己半找半摸索累惨了。。。 ①登录地址:IP:15000/#/login     注

    2024年02月08日
    浏览(80)
  • JavaScript中的20个小技巧

    箭头函数可在没有参数时更简洁 箭头函数用于定义并创建函数的常用方式。如果没有参数,则可以使用下面的代码: 使用setInterval()实现计时器 setInterval()是一个用于重复调用函数的内置函数。下面是一个setInterval()计时器的例子: 数组查找/搜索 使用Array.find()方法可以快速查

    2023年04月08日
    浏览(29)
  • Laya小游戏开发,laya3D美术篇——1——关于laya自带的几个shader的基础运用讲解。

    最近三年,基本上做的都是laya小游戏项目。也就是微信小程序,很多业内同行都觉得laya做小游戏不好用,去用了其他平台,甚至还有些做app游戏的,都不来趟laya这个坑。原因有那么以下几点。laya对于unity的辅助开发,仅仅给unity支持了几个效果相对凑合的shader。并且,laya跟

    2024年02月08日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包