// 控制器/Service 混雜大量樣板代碼
if (request.getId() == 0) { // 新增
request.setCreatorId(userId);
request.setCreatorName(userName);
request.setCreateTime(new Date());
}
request.setModifierId(userId);
request.setModifierName(userName);
request.setModifyTime(new Date());
// 還有默認值處理:null → 0/""/1990-01-01...
問題:業務邏輯被稀釋、重複代碼多、修改字段名需全局搜尋替換、違反單一職責。
Spring MVC 提供 HandlerMethodArgumentResolver 擴展點,攔截控制器方法參數綁定階段,**在參數進入業務邏輯前自動注入系統字段與默認值**。
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ManageModelConfig {
boolean isSetDefaultFieldsValue() default true; // 是否處理默認值
}
@Component
public class ManageModelConfigResolver implements HandlerMethodArgumentResolver {
@Autowired private RequestMappingHandlerAdapter handlerAdapter;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(ManageModelConfig.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 1. 先讓 Spring 完成常規綁定 (JSON → POJO)
RequestResponseBodyMethodProcessor processor = getProcessor();
Object model = processor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
// 2. 獲取當前登入用戶 (假設存於 Request 屬性)
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Employee currentUser = (Employee) request.getAttribute("CURRENT_USER");
if (currentUser == null) return model;
// 3. 讀取註解配置
ManageModelConfig config = parameter.getParameterAnnotation(ManageModelConfig.class);
// 4. 統一處理:默認值 + 系統字段
ModelHelper.injectSystemAndDefaultFields(model, currentUser, config.isSetDefaultFieldsValue());
return model;
}
private RequestResponseBodyMethodProcessor getProcessor() {
// 懶加載構建 Processor,複用 MessageConverter
// ... 略
}
}
public class ModelHelper {
// 系統字段命名約定
private static final String CREATE_ID = "creatorId";
private static final String CREATE_NAME = "creatorName";
private static final String CREATE_TIME = "createTime";
private static final String MODIFY_ID = "modifierId";
private static final String MODIFY_NAME = "modifierName";
private static final String MODIFY_TIME = "modifyTime";
public static void injectSystemAndDefaultFields(T model, Employee user, boolean setDefault) {
Class> clazz = model.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String name = field.getName();
Class> type = field.getType();
try {
// --- 默認值處理 ---
if (setDefault && field.get(model) == null) {
if (type == String.class) field.set(model, "");
else if (type == Integer.class || type == int.class) field.set(model, 0);
else if (type == Long.class || type == long.class) field.set(model, 0L);
else if (type == BigDecimal.class) field.set(model, BigDecimal.ZERO);
else if (type == Date.class) field.set(model, Date.from(LocalDateTime.of(1990,1,1,0,0).atZone(ZoneId.systemDefault()).toInstant()));
}
// --- 系統字段處理 ---
boolean isCreate = false;
try {
Field idField = clazz.getDeclaredField("id");
idField.setAccessible(true);
Object idVal = idField.get(model);
isCreate = (idVal == null || (idVal instanceof Number && ((Number)idVal).longValue() <= 0));
} catch (NoSuchFieldException ignored) {}
if (isCreate) {
if (name.equals(CREATE_ID)) field.set(model, user.getId());
if (name.equals(CREATE_NAME)) field.set(model, user.getName());
if (name.equals(CREATE_TIME)) field.set(model, new Date());
}
// 修改字段無論新增/編輯都更新
if (name.equals(MODIFY_ID)) field.set(model, user.getId());
if (name.equals(MODIFY_NAME)) field.set(model, user.getName());
if (name.equals(MODIFY_TIME)) field.set(model, new Date());
} catch (IllegalAccessException e) {
log.warn("Field inject failed: " + name, e);
}
}
}
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired private ManageModelConfigResolver resolver;
@Override
public void addArgumentResolvers(List resolvers) {
resolvers.add(resolver);
}
}
或 XML:<mvc:argument-resolvers><bean class="..."/></mvc:argument-resolvers>
@PostMapping("/save")
@ResponseBody
public Result save(@ManageModelConfig(isSetDefaultFieldsValue = true) EmployeeDTO dto) {
if (dto.getId() == 0) employeeService.insert(dto);
else employeeService.update(dto);
return Result.ok();
}
**零系統字段代碼、零默認值代碼、純業務邏輯**。
| 決策點 | 方案 | 理由 |
|---|---|---|
| 識別新增/編輯 | ID > 0 為編輯,否則新增 | 通用、無需額外標記 |
| 系統字段命名 | 約定式 (creatorId/modifyTime...) | 零配置、統一規範 |
| 獲取當前用戶 | Request 屬性 (Filter/Interceptor 寫入) | 無侵入、支援集群 Session 共享 |
| 默認值處理開關 | 註解參數控制 | 靈活、向後相容 |
| 反射效能 | 字段緩存 (ConcurrentHashMap<Class, Field[]>) | 避免重複 getDeclaredFields |