Java/Spring Http Interceptor中设置Model对象的方式是否正确?
首先得说,你通过拦截器(Interceptor)的preHandle方法提前调用Service层设置对象属性的思路是完全可行的——preHandle确实会在请求到达控制器之前执行,所以能完成你想要的前置设置操作。不过你的代码存在几个需要注意的问题,尤其是线程安全隐患,我帮你拆解下:
1. 依赖注入的最佳实践问题
你的拦截器和控制器里注入的都是ArticleServiceImpl(实现类),但Spring依赖注入的最佳实践是依赖抽象而非具体实现,也就是应该注入ArticleService接口。这样做的好处是后续如果需要替换Service的实现类,不需要修改依赖它的代码,更符合开闭原则。
修改很简单,把注入的类型换成接口即可:
// 拦截器里 @Autowired private ArticleService articleService; // 控制器里 @Autowired private ArticleService articleService;
2. 严重的线程安全问题(最关键)
Spring默认的Bean是单例模式,ArticleServiceImpl作为Service也是单例的,而你在Service里直接声明了成员变量Article article=new Article();。这就意味着所有请求都会共享同一个Article对象——当多个请求同时进来时,并发修改name属性会导致数据混乱:比如请求A刚把name设为"article 123",请求B马上改成"article 456",这时候请求A的控制器拿到的可能是B设置的值,完全不符合预期。
解决方法:用ThreadLocal隔离线程
我们可以用ThreadLocal来为每个线程维护独立的Article实例,这样每个请求的修改都只会影响自己线程内的对象:
@Service public class ArticleServiceImpl implements ArticleService { // 用ThreadLocal初始化每个线程独有的Article对象 private ThreadLocal<Article> articleThreadLocal = ThreadLocal.withInitial(Article::new); @Override public void setArticleName(String name) { articleThreadLocal.get().setName(name); } @Override public String getArticleName() { return articleThreadLocal.get().getName(); } // 可选但建议:请求结束后清理ThreadLocal,避免内存泄漏 public void clearArticle() { articleThreadLocal.remove(); } }
然后在拦截器的afterCompletion方法里调用清理方法,确保请求结束后释放ThreadLocal里的资源:
@Component public class MySecurityFilter extends HandlerInterceptorAdapter { @Autowired private ArticleService articleService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) { System.out.println("in interceptor"); articleService.setArticleName("article 123"); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 清理ThreadLocal资源 if (articleService instanceof ArticleServiceImpl) { ((ArticleServiceImpl) articleService).clearArticle(); } } }
3. 拦截器的注册问题
你只写了拦截器类,但如果没在Spring配置里注册它,这个拦截器根本不会生效!这是新手很容易忽略的点。你需要创建一个Web配置类,把拦截器添加到拦截器链中,并指定要拦截的路径:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private MySecurityFilter mySecurityFilter; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(mySecurityFilter) .addPathPatterns("/api/**"); // 拦截所有/api开头的请求,匹配你的控制器路径 } }
4. 代码完整性问题
你的MySecurityFilter类的代码看起来没写完,缺少闭合的大括号,记得补上,否则编译会报错。
总结
你的核心思路是正确的,但修复上述几个问题后,代码才能在单线程和高并发场景下都正常工作。尤其是线程安全问题,一定要重视,否则上线后会出现难以排查的并发bug。
内容的提问来源于stack exchange,提问作者Piyush arora

