首页 > 编程语言 >【进阶篇】Java 实际开发中积累的几个小技巧(二)

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

时间:2024-04-16 10:11:22浏览次数:29  
标签:Java 技巧 自定义 接口 进阶篇 切面 注解 抽象类 public

目录

前言

笔者目前从事一线 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,技巧,自定义,接口,进阶篇,切面,注解,抽象类,public
From: https://www.cnblogs.com/CodeBlogMan/p/18135597

相关文章

  • JAVA各种系统架构图及其简介
    JAVA各种系统架构图及其简介1.spring架构图 Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框架。Spring框架的功能可以用在任何J2EE服务器中,大多数......
  • Java架构核心基础知识硬核整理,赶快收藏起来吧
    Java架构核心基础知识硬核整理,赶快收藏起来吧Java架构核心基础lecture:波哥一、数据结构和算法1.数据结构  数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者......
  • JAVA各种系统架构图,终于有人把Java程序员必学知识点全整理出来了
    JAVA各种系统架构图,终于有人把Java程序员必学知识点全整理出来了1.spring架构图Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框架。Spring框架的功能......
  • Java分布式架构:应用+特点+架构模式
    Java分布式架构是一个复杂的主题,它涉及到许多不同的概念和技术。在本文中,我们将介绍Java分布式架构的应用、特点和架构模式,以便更好地了解该主题。应用:Java分布式架构可以应用于许多不同的场景,例如:电子商务网站:电子商务网站需要处理大量的交易和订单,而Java分布式......
  • day11_我的Java学习笔记 (static_静态成员变量+静态成员方法_工具类、代码块_静态代码
    0.面向对象进阶1.static静态关键字1.1static是什么,static修饰成员变量的用法Java成员变量成员方法Python类(对象)属性类(对象)方法static修饰成员变量的应用:在线人数统计1.2static修饰成员变量的内存原理1.3static修饰成员方法的基本......
  • day10_01_我的Java学习笔记 (JavaSE进阶课程预备)
    JavaSE进阶课程预备1.JavaSE加强课程简介2.IDEA开发模式统一工程,相当于一个小区的院子;模块,是小区的哪一栋;包,是这栋楼的那一单元类,是这个单元的哪一层楼;对象,是这层楼具体的某一户房间。eg:滢水山庄二区--工程9栋--模块4单元--包8楼--类......
  • day10_02_我的Java学习笔记 (JavaSE加强课程介绍、先建空工程--再建模块--然后建包--
    JavaSE基础加强课程介绍1.JavaSE加强课程简介2.IDEA开发模式统一工程,相当于一个小区的院子;模块,是小区的哪一栋;包,是这栋楼的那一单元类,是这个单元的哪一层楼;对象,是这层楼具体的某一户房间。eg:溪山美地二区--工程9栋--模块4单元--包8楼--......
  • day08_我的Java学习笔记 (String类、ArrayList集合类)
    常用API(String、ArrayList)什么是APIAPI文档下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html1.String简单介绍【补充】:为什么java数据类型String是大写?1.1String类概述1.2String类创建对象的2种方式1.3String......
  • day09_我的Java学习笔记 (ATM系统_理解并学会使用break、return、continue、Random、S
    ATM系统1.项目介绍与功能演示1.1系统准备、首页设计Account.java每个用户的账户信息都是一个对象,需要提供账户类。01.在Account类中定义系统相关的属性信息(卡号、姓名、密码、余额、取现额度)02.定义Getter和Setter方法03.定义有参构造器04.定义无参构造......
  • Java中LinkedList
    LinkedList的长度是可变的。LinkedList是Java中的一个双向链表实现,它可以动态地增加或减少元素,因此其长度是可变的。这种动态性使得LinkedList在需要频繁插入或删除元素时具有一定的优势,因为它不需要像数组那样进行元素的移动操作。当你向LinkedList中添加或删除元素时,它......