Spring Boot应用程序的代码审查要点
最近,我在工作中越来越多地接触到Spring Boot。使用Spring时,如果善用注解和相关功能,可以以相对高的生产力构建应用程序,但是如果在某种程度上盲目使用,可能会导致意外行为的发生。这次我打算整理一下在代码审查时经常评论的内容(虽然都是基础知识点……)。
(1) 是否将控制器/服务/存储库进行分割?
在Spring中,建议将整个应用程序的处理拆分为以下三层结构的类进行实现,而不是将其硬编码在特定的类中。
因为Controller成为请求的入口,所以我认为对于初次接触Spring的开发者来说,它应该非常熟悉。但是,如果我们只是盲目地开发应用程序,很容易在Controller中实现业务逻辑。这样做会导致所谓的肥大Controller,低可读性和可维护性,所以我认为我们应该强烈意识到业务逻辑的隔离。
我个人认为,可以专注于以下各项内容是比较好的想法。
-
- Controller
リクエストの受付
リクエストパラメータのValidation
Serviceの実行
例外処理
レスポンスの作成
Service
ビジネスロジックの実行
外部サービスとの連携
Repositoryを介したデータ操作
トランザクション管理
Repository
データ操作
如果在控制器中调用多种类型的服务,由于控制器可能变得庞大,所以在这种情况下,我觉得可以在控制器和服务之间创建一个服务门面类来进行中介。
考虑实例的生命周期实现了吗?
在Spring的DI容器中管理的实例,默认情况下是单例的。换句话说,如果在成员变量等地方保存状态,那么在访问同一应用程序的用户之间,状态(如用户ID等)会混在一起,往往导致预期行为无法实现。基本上,我认为最好是以无状态(Stateless)方式创建,但根据需求,也可以考虑更改为Spring支持的其他范围。
顺便说一下,如果是自定义类,可以通过添加@Scope注解来更改作用域。
@Scope("prototype")
@Service
public class SecretService {
//処理実装
}
作为检查要点
-
- Singletonなインスタンスで状態を持っていないか?
- 不必要に他のスコープ(session/prototype)を使っていないか?
附近开始成为重点区域。
你是否使用了构造器注入?
使用Spring时,无法避免的依赖注入(DI)中,有两种方式可以不编写配置文件。※虽然可能仍然存在使用XML定义,但我们认为使用它的人很少,所以不详述。
-
- フィールドインジェクション
- コンストラクタインジェクション
在中国,只需要一种选择来将以下内容进行中文本地化:
字段注入只需在成员变量上添加@Autowired,非常简单。
@Autowired
private SecretService service;
然而,由于以下列出的缺点,它已被不推荐使用。※我记得Spring也曾发出警告。
-
- テストコード作成時にDIコンテナを使わないとモックが出来ない
- フィールドにfinal属性を付与出来ずimmutableにならない
因此,除非有非常特殊的情况(例如必须要循环依赖),我们应该使用构造函数注入。
private final SecretService service
public MyClass(SecretService service) {
this.service = service;
}
如果构造函数的定义太繁琐的话,利用Lombok也是一种选择。
@AllArgsConstructor
public class MyClass {
//TODO: 実装
}
(4) 是否有可进行共通化的处理方法?
在Spring框架中,提供了HandlerInterceptor和AOP等机制来定义横切性的通用处理。通过利用这些机制,可以实现以下功能:
-
- 特定パッケージ配下にある全てのクラスの全メソッドで共通の前処理を行う
- 特定の型の戻り値を返すメソッドで共通の後処理を行う
在某些情况下,您不再需要实现重复的处理。常见的情况可能是日志输出。
/**
* ロガークラス
*/
private final Logger logger = LoggerFactory.getLogger(MyController.class);
/**
* リクエストメソッドのサンプル.
* @param form Formクラス
* @param bindingResult Validation結果
* @return レスポンス
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String handleRequest(@Valid MyControllerForm form, BindingResult bindingResult) {
logger.info("Interceptorテスト");
//省略
}
}
详细地
@Aspect
@Component
public class MyInterceptor {
@Before("execution(* jp.co.cross_xross.controller..*.*(..))")
public void beforeProcess(JoinPoint joinPoint) throws Throwable {
//前処理
}
}
通过指定包名、方法名、参数和返回值,可以执行”前处理”、”后处理”和”替代处理”之类的操作。
(5) 是否没有自行实现Spring提供的功能?
我觉得差不多没有梗了(笑),Spring常常将常见的功能作为子项目提供。如果使用该功能,可以轻松地实现自己实现时常遇到的困难部分,所以我认为应该利用它。
附言…
下面是需要结合Java的基本代码审查要点来确认的地方。作为纯Java代码审查的视角,我参考了之前在qiita上写的一些前辈们的文章。
-
- うまくメソッド名を付けるための参考情報
-
- ちょっといいJavaコードを書こう
- コードレビュー チェックリスト