V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
rizon
V2EX  ›  程序员

spring-best-practices 总结了下这几年 Spring 开发的一些优雅实现

  •  2
     
  •   rizon ·
    othorizon · 2019-12-25 21:50:29 +08:00 · 6520 次点击
    这是一个创建于 1819 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Github 地址: https://github.com/othorizon/spring-best-practices


    spring 最佳实践

    总结了本人 3 年 Java 开发中的一些开发经验和工具类以及 Spring 框架的应用
    采用了 Spring 项目的模式来最简单直观的呈现,直接拿来作为初始化项目也是不错的选择
    该项目还在打磨中,仍有很多需要完善和优化的地方

    持续更新,欢迎 PR

    概要

    • 如何配置拦截器:interceptor、filter、 @RestControllerAdvice
    • bean 的初始化:InitializingBean 接口、 @conditionXXX 注解
    • 如何获取 applicationContext 上下文:ApplicationContextAware
    • 枚举的优雅使用:1、如何把枚举作为接口的交互参数:@JsonCreator、 @JsonValue ,要注意 fastjson 和 spring 采用的 jackson 对注解的支持 2、valueOfByXX
    • 缓存的优雅使用: @Cacheable、CaffeineCacheManager、请求级别的缓存 RequestScopedCacheManager,注意防止副作用操作污染缓存数据
    • 配置文件配置时间属性:java.time.Duration
    • 正确的报错方式,message 的国际化
    • 日志的优雅配置:log4j 与 logback 的基础、使用 MDC 增加 tractId 跟踪日志

    第三方工具的使用

    • RestTemplate 的优雅使用:工具类的封装、header 的注入
    • 借助 Mybatis-Plus 实现零 SQL 开发
    • 借助 MapStruct 实现 po、bo、vo 等对象之间的转换
    • 健康接口,版本检查:buildnumber-maven-plugin
    • 如何深度复制对象:json 复制、mapStruct
    • apache-common 系列、hutool 等基本工具类
    • 自定义的时间格式化工具类、jackson 的封装工具

    运维

    • 数据库版本维护 flyway
    • Jenkins 的常用配置方式

    Github 地址: https://github.com/othorizon/spring-best-practices

    先起个头,后面会花时间去整理、沉淀和打磨的。

    不知道这东西到底有没有写的价值,所以有些小纠结, 不过对别人来说也许毫无用处,但是对我来说却是非常有意义的了。
    如果您觉得这里面有能对你有点用处的东西,麻烦给个 star 支持下哈~~

    41 条回复    2019-12-27 22:26:04 +08:00
    rizon
        1
    rizon  
    OP
       2019-12-25 21:55:21 +08:00   ❤️ 1
    v 站大佬太多,其实怕大家笑话,有些不太敢发帖的,唉~~
    chenshun00
        2
    chenshun00  
       2019-12-25 22:55:43 +08:00
    提交的概要好像没有啥是我需要的 :(
    crclz
        3
    crclz  
       2019-12-25 23:28:01 +08:00
    star 一哈
    a852695
        4
    a852695  
       2019-12-26 00:08:52 +08:00
    不错 正在学习 java
    wysnylc
        5
    wysnylc  
       2019-12-26 00:52:36 +08:00
    配置和工具交流可还行....
    Sunyanzi
        6
    Sunyanzi  
       2019-12-26 01:58:15 +08:00
    看到这个列表我突然想到一个问题 ... 使用 Interceptor 的正确姿势到底是什么 ..?

    我个人一直在用 AOP 实现类似的功能 ... 而且感觉语法更优雅一些 ...

    有哪些需求是切面织入无法完成必须要用拦截器的吗 ..?
    beginor
        7
    beginor  
       2019-12-26 08:29:03 +08:00 via Android
    居然没有 Spring.Security 相关的?
    Takamine
        8
    Takamine  
       2019-12-26 08:56:59 +08:00 via Android
    @Sunyanzi 我感觉单从前置来说。……你想要对一个妹纸伸手,拦截器是装了道门 AOP 是穿了件衣服吧。:doge:
    magicnobob
        9
    magicnobob  
       2019-12-26 09:07:56 +08:00
    很好,向大佬学习,spring 要多学多用
    diagram2048
        10
    diagram2048  
       2019-12-26 09:31:21 +08:00
    向大佬学习
    FanError
        11
    FanError  
       2019-12-26 09:37:19 +08:00
    @beginor Spring Security 太复杂了,感觉很多人都用拦截器,AOP 自己实现权限等验证操作。
    securityCoding
        12
    securityCoding  
       2019-12-26 09:39:25 +08:00
    @Sunyanzi aop 做的事情多一些,interceptor 针对的是 http 调用链
    rizon
        13
    rizon  
    OP
       2019-12-26 11:14:18 +08:00
    @Sunyanzi #6 嗯,还有 aop,把这个给忘了,回头补上 aop 的使用,
    个人觉得拦截器是专门专事,aop 则就是更粗暴的去解决了问题,有失优雅
    我只拿 aop 写过打印方法执行时间的日志,和自定义注解的处理。今天找个时间加上哈
    rizon
        14
    rizon  
    OP
       2019-12-26 11:19:57 +08:00
    @Sunyanzi #6 java 写代码就是要把事情说清楚,说的有规矩有框子,虽然不够潇洒自由,但对于企业级的项目,可维护性、健壮性才是最重要的,避免技术负债。所以 aop 在可维护性上是低于拦截器的,而且因为 AOP 的手脚太长,容易被错误使用带来无法预期的和难以排查的问题
    palmers
        15
    palmers  
       2019-12-26 11:20:52 +08:00
    还有 spring profile 以及 bean 声明的 xml 头的一些 default 配置 也挺好用的 很多场景可以使用到 可以一定程度上做到依赖分离处理 统一异常处理等等 如果整理需要全面一些,个人觉得是很有意义的 支持你
    palmers
        16
    palmers  
       2019-12-26 11:21:19 +08:00
    哦对了 记得加上 spring 对应版本范围
    Allianzcortex
        17
    Allianzcortex  
       2019-12-26 11:22:50 +08:00 via iPhone
    rizon
        18
    rizon  
    OP
       2019-12-26 11:28:04 +08:00
    @palmers #15 感谢支持,Spring 的 IOC 方面的东西 我会好好整理一下哈,
    spring boot 用的是 2.1.X,本来想用的 2.2 的,不过考虑到之前项目一直用的 2.1,那时候 Spring cloud 的兼容 2.2 版本的 Hoxton 还没有出,所以还是继续用 2.1 吧
    rizon
        19
    rizon  
    OP
       2019-12-26 11:41:38 +08:00
    @FanError #11
    @Allianzcortex #17

    我在项目中一直用的是拦截器,在我整理的这个项目里也写了一个简单的 auth 实现,特点是解耦。
    aop 是针对方法的,拦截器则是针对 http 请求的,我们的权限认证都是针对接口请求的。

    我们的权限认证服务是单独开发的,采用 sdk 的方式从公司的私有 maven 仓库分发到各个组件的服务,使用方只要把 sdk 中的拦截器注册进来就可以了。
    这样代码侵入最低,当然这种方式也有他的适用范围,各个项目的开发都有自己的不同处境,aop 也有它独有的优势。
    m1862897
        20
    m1862897  
       2019-12-26 12:00:26 +08:00
    看了一下,其中的 mapstruct 非常不好用

    spring 本身就提供 beansutil 做拷贝,你这个画蛇添足
    另外 hutool 是好东西,比 google 的 guava 还好用,值得推荐

    其他的都是 spring 的基本用法,不值一提
    rizon
        21
    rizon  
    OP
       2019-12-26 12:08:44 +08:00
    @m1862897 #20
    hutool 确实提供了很多好东西
    mapstruct 也有他的优势,mapstruct 工作原理上完全不同,是预编译的方式,虽然缺少了动态执行的灵活性,但也确保了可靠性,而且效率上也是高于反射的。
    我用 mapstruct 的原因就是它公开透明、可控。而且也提供了一些可以做复杂映射的配置。
    不要因为代码写起来复杂不够简洁就放弃。

    当然有些场景下我也会用 beanutils,还是看场景吧,融会贯通嘛
    lx91714
        22
    lx91714  
       2019-12-26 12:10:21 +08:00 via Android
    很不错哦
    Allianzcortex
        23
    Allianzcortex  
       2019-12-26 12:45:51 +08:00 via iPhone
    @rizon @FanError 理解。如果只针对 HTTP 方法的话两者没什么特别的优劣。我用 AOP 实现的是 authorization,用 interceptor 和 filter 也实现了 authentication https://github.com/Allianzcortex/LaraForum/tree/master/src/main/java/com/laraforum/authentication
    BoomMan
        24
    BoomMan  
       2019-12-26 13:15:24 +08:00
    我也有相关的积累,想贡献自己的一部分代码,微信沟通下,哪部分比较合适吧. Wx TG92ZV9Zb3VfODAyMw==
    lihongjie0209
        25
    lihongjie0209  
       2019-12-26 13:29:24 +08:00
    @m1862897 #20 mapstruct 是代码生成实现拷贝, beanutil 是反射拷贝.
    代码生成是类型安全的, beanutil 不是.
    代码生成是可以重构的, beanutil 不能.
    代码生成目前执行效率最高, 反射最慢.
    代码生成可以重写成生成代码的方法, 定制化灵活, 反射不行
    BoomMan
        26
    BoomMan  
       2019-12-26 13:36:19 +08:00
    @rizon 我也有相关的积累,想贡献自己的一部分代码,微信沟通下,哪部分比较合适吧. Wx TG92ZV9Zb3VfODAyMw==
    BoomMan
        27
    BoomMan  
       2019-12-26 14:18:00 +08:00
    @rizon 提了一个 pr,之前很少提,流程不是很熟悉,如果有问题可以进一步沟通哈
    chendy
        28
    chendy  
       2019-12-26 14:32:42 +08:00
    两三年前试过 mapstruct 和另外几个用 apt 的拷贝…
    可能是 idea 支持不好,代码更新总是不及时,加了注解除非 clean 掉重新编译否则一直不生效
    看到楼主推荐,打算再拿出来试试
    rizon
        29
    rizon  
    OP
       2019-12-26 15:20:32 +08:00
    @BoomMan #26 感谢支持哈,我看了下 pr,您提的内容有些重了,这毕竟是个 demo 程序哈,
    另外就是参数校验的代码我其实也在整理精简,所以冲突了哈,抱歉哈,嘿嘿😁。
    现在已经 push 了,您也可以参考一下,基本都是一样的,但是我加了一个错误信息的国际化处理,也就是`ValidationMessages.properties`配置错误信息。
    后面我还会提交一些自己写的参数校验注解,比如枚举校验等

    再有就是您提到的 @ConfigurationProperties 在 yml 配置里没有提示的问题,
    虽然采用编写 META-INF 可以解决,不过 idea 其实是有提示的,引入了`spring-boot-configuration-processor` 之后在 idea 的设置里 'Annotation Processors' 中勾选为 enable 就可以了,每次 build 的时候会更新提示,这和 lombok 一样都依赖这个 processor 的处理
    rizon
        30
    rizon  
    OP
       2019-12-26 15:23:17 +08:00
    @chendy #28 因为是预编译的,会生成实现类在`target/generated-sources/annotations`文件夹,所以有时候是需要 clean 一下的
    BoomMan
        31
    BoomMan  
       2019-12-26 15:33:15 +08:00
    @rizon ok,我理解的是需要一个清楚明了的例子,如果全部整合在一起不太好。
    Sunyanzi
        32
    Sunyanzi  
       2019-12-26 15:58:04 +08:00
    @Takamine 啊前辈好 ... 我努力琢磨了十分钟也没明白这个逻辑关系 ...

    装一道门还可以理解成需要开关门有个类似 threshold 的东西 ... 穿了件衣服是啥啦 ...

    @securityCoding 明白 ... 我之前只知道到在 SpringMVC 之外没地方配置 Interceptor ... 现在就更清晰了 ...

    @rizon 我不太理解这个「专门专事」和「更粗暴」包括「手脚太长」的结论是如何得到的 ... 可否举例 ..?

    在我看来定义一个 Interceptor 之后还要去 XML 或者 Configuration 里面注册与配置应用场景 ...

    修改拦截规则也需要修改配置 ... 这远不如对自定义注解进行切面织入来得优雅 ..? 是我理解错了什么吗 ..?
    967182
        33
    967182  
       2019-12-26 16:04:04 +08:00
    Mybatis-Plus 实现零 版本升级头疼吗?
    Takamine
        34
    Takamine  
       2019-12-26 19:29:17 +08:00 via Android
    @Sunyanzi ……你才是前辈。_(:з」∠)_
    我觉得拦截器是还没有进入到实际的业务领域中就处理了,和应用实际上某种程度松耦合了,而用 AOP 织入的时候,就已经是去代理业务领域的对象了。
    rizon
        35
    rizon  
    OP
       2019-12-26 20:38:41 +08:00
    @Sunyanzi #32
    aop 缺少可控性,aop 是针对一个可以模糊匹配的 path 去做拦截的,但是 path 本身是没有意义的,也就是说只有写这个代码的你知道他的业务上的意义。我个人看来,这就是大忌。 如果误伤了咋办,你了解代码你不会误伤,但是不能避免后来人在缺少对代码的足够理解的情况下被误伤了。
    而且后续的人维护这类代码还是很头疼的,不敢乱动。
    但是拦截器就是针对某个业务场景去拦截,天然意义上就不会出现这种情况。

    换言之,拦截器是低耦合的,代码侵入性低。
    rizon
        36
    rizon  
    OP
       2019-12-26 20:41:02 +08:00
    @967182 #33 版本升级的场景倒是没有遇到过
    buliugu
        37
    buliugu  
       2019-12-27 09:35:51 +08:00
    @967182 Mybatis-Plus 升级要慎重,前后版本兼容性不是很好
    zppass
        38
    zppass  
       2019-12-27 10:48:21 +08:00
    再加一个分页的插件,pageHelpr。关于拦截器,我感觉更多是请求拦截,文件后缀限制,粒度可能达不到 AOP 的粒度那么细那么灵活(实际拦截器也能写得更细节,但是失去了作为通用配置的作用),这是我的想法。
    rizon
        39
    rizon  
    OP
       2019-12-27 11:11:06 +08:00
    @zppass #38 现在案例中用的是 mybatis-plus 所以不需要和这个去分页,
    不过我也会加上的,我会加一个手写 sql 的案例,通过 pageheleper 注入分页数据, 以及写一个和 pagehelper 同原理的动态表名工具
    rizon
        40
    rizon  
    OP
       2019-12-27 14:54:03 +08:00
    @zppass #38 pagehelper 插件的使用加上了,也加了一个按日期等方式做分表时的动态表名查询的解决方案
    967182
        41
    967182  
       2019-12-27 22:26:04 +08:00
    @buliugu 是的,变化好大!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4834 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 09:50 · PVG 17:50 · LAX 01:50 · JFK 04:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.