SSH(Struts2 + Spring + Hibernate)架構下,需根據用戶權限、API 版本或客戶端類型,動態決定 JavaBean 序列化 JSON 時輸出哪些屬性。例如:管理員看完整字段,普通用戶隱藏敏感欄位(薪資、身分證號)。
可存放於資料庫、YAML 或註解中。範例採用註解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonView {
String[] roles() default {}; // 允許的角色
String[] profiles() default {}; // 允許的配置檔
}
實體類使用:
public class Employee {
private Long id;
private String name;
@JsonView(roles = {"ADMIN", "HR"})
private BigDecimal salary;
@JsonView(roles = {"ADMIN"})
private String idCard;
// getter/setter
}
public class JsonFilterConfigFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
// 從 Session/Token 獲取當前用戶角色
Set roles = SecurityContext.getCurrentRoles();
String profile = request.getHeader("X-Client-Profile"); // 如 "mobile", "admin"
// 存入 ThreadLocal,供序列化器讀取
JsonSerializationContext.setContext(new SerializationContext(roles, profile));
try {
chain.doFilter(req, res);
} finally {
JsonSerializationContext.clear(); // 防洩漏
}
}
}
在 web.xml 註冊(或 Spring Boot 中 @WebFilter):
<filter>
<filter-name>jsonFilterConfig</filter-name>
<filter-class>com.example.filter.JsonFilterConfigFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>jsonFilterConfig</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
public class DynamicBeanSerializerModifier extends BeanSerializerModifier {
@Override
public List changeProperties(
SerializationConfig config,
BeanDescription beanDesc,
List beanProperties) {
SerializationContext ctx = JsonSerializationContext.getContext();
if (ctx == null) return beanProperties; // 無上下文則全輸出
return beanProperties.stream()
.filter(writer -> isPropertyVisible(writer, ctx))
.collect(Collectors.toList());
}
private boolean isPropertyVisible(BeanPropertyWriter writer, SerializationContext ctx) {
JsonView view = writer.getAnnotation(JsonView.class);
if (view == null) return true; // 無註解預設可見
// 角色匹配
if (view.roles().length > 0) {
boolean match = Arrays.stream(view.roles()).anyMatch(ctx.getRoles()::contains);
if (!match) return false;
}
// 配置檔匹配
if (view.profiles().length > 0) {
boolean match = Arrays.stream(view.profiles()).anyMatch(p -> p.equals(ctx.getProfile()));
if (!match) return false;
}
return true;
}
}
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new DynamicBeanSerializerModifier());
mapper.registerModule(module);
return mapper;
}
}
| 方案 | 優點 | 缺點 |
|---|---|---|
| 手動構建 DTO/VO | 完全可控 | 類爆炸、維護成本高 |
| Jackson @JsonView(靜態) | 原生支援 | 無法根據運行時角色動態決定 |
| 本方案(動態過濾器) | 運行時決策、無 DTO、效能好 | 需理解 Jackson 進階擴展點 |
結合 Servlet Filter 建立序列化上下文,配合 Jackson 的 BeanSerializerModifier,即可在 SSH/Spring Boot 架構下實現優雅、高效、可維護的 JavaBean 屬性動態過濾。此模式同樣適用於 XML、Protobuf 等其他序列化協議。