spring-best-practices 总结了下这几年 Spring 开发的一些优雅实现 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
rizon
V2EX    程序员

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

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

    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     Solana     4241 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 04:08 PVG 12:08 LAX 21:08 JFK 00:08
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86