SpringMVC的Action在同一时间里只允许同一个浏览器的单次进入?

最近用SpringMVC写了一个很简单的测试程序,代码如下:

@Controller
public class LongTimeTaskController {
    @RequestMapping(value = "/sleep", method = RequestMethod.GET)
    public ModelAndView sleepTest(){
        System.out.println(" begin sleep. " + Thread.currentThread().getId());
        try {
            Thread.sleep(15000);
        } catch (InterruptedException e) {
        }
        System.out.println(" end sleep and return. " + Thread.currentThread().getId());
        return new ModelAndView("sleep");
    }
}

这个sleepTest()的Action意图很明确,对它的调用将sleep 15秒钟再返回。接下来我用Chrome打开多个tab,打入“http://localhost:8080/sleep”进行访问,很快我发现了个奇怪的现象,那就是标题所描述的那样,sleepTest()这个Action同一时间里只有一个线程在跑,也就是说,如果我几乎同时让A、B、C三个Tab打开这个地址(事实上,A略微先于B,B略微先于C),我就观察到,A成功返回的时候(看到“ end sleep and return.”),B才开始进入(看到“ begin sleep.”),B成功返回的时候C才开始进入,等到C成功返回的时候一共花了不下45秒钟,很奇怪!

难道不支持多线程吗?我把执行它们的线程ID打印了出来,发现是不同的线程,接下来进一步研究发现了:

1,sleep阻塞的时候,对别的Action的访问并不受影响
2,如果启动别的浏览器,访问sleep,也不会被阻塞

这看起来像是SpringMVC的限制,因为这跟是否访问同一个Action有关,但实在搞不懂为什么要这样,最好就是Google一下,这种问题如果存在,问的人应该很多,但很遗憾,Google不到任何相关内容。

后来想想,干嘛不用Postman试试看呢?一尝试,惊奇地发现Postman没有这个问题,我的天,这竟然是浏览器的限制?答案是肯定的,接下来我很快发现了IE并没有这个限制,这个奇怪的限制只在Chrome上出现了,要证明很简单,用Wireshark抓一下包即可。

Chrome的限制规则是:浏览器同时只能对同一个URL提出一个请求,如果有更多的请求的话,对不起,请排队。这个所谓“限制”到底好不好?可能不错,想想对同一URL的请求,如果前请求阻塞,那么后请求想必也会被阻塞,这无端增加了开销,并没多大意义,Chrome这么做应该有它的合理性。

转载于:https://www.cnblogs.com/guogangj/p/5238915.html

JavaWeb项目中,使用SpringSpring MVC和MyBatis作为技术栈,Shiro作为安全框架,实现一个用户同时只能在一个终端浏览器上登录,可以借助Session管理和令牌验证来达成这一目标。这提供一个简单的示例代码: 首先,在UserDetailsServiceImpl中,利用HttpSession存储登录状态: ```java @Service("userDetailsService") public class UserDetailsServiceImpl implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 实现获取用户信息并设置session UserDetails userDetails = ...; HttpSession session = WebUtils.getHttpRequest().getSession(); session.setAttribute("currentUser", userDetails); return userDetails; } // 添加对Session过期的检查 public boolean isLoginValid(HttpSession session) { return session != null && !session.isNew() && session.getAttribute("currentUser") != null; } } ``` 然后,在拦截器或Filter中,检查用户是否已经登录以及是否在其他地方登录: ```java @Component @Order(Ordered.HIGHEST_PRECEDENCE) public class LoginCheckFilter extends OncePerRequestFilter { private final UserDetailsService userDetailsService; public LoginCheckFilter(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { if (isLoginValid(request.getSession())) { throw new UnauthorizedException("用户已在他处登录"); } chain.doFilter(request, response); } private boolean isLoginValid(HttpSession session) { return userDetailsService.isLoginValid(session); } } @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(UnauthorizedException.class) public String handleUnauthorized(Exception e) { return "redirect:/login?message=" + e.getMessage(); } } ``` 最后,在`/login`控制器中,处理登出操作时清除当前会话: ```java @PostMapping("/logout") public String logout(@RequestParam(value = "message", defaultValue = "") String message) { HttpServletRequest request = ServletActionContext.getRequest(); HttpSession session = request.getSession(); if (session != null) { session.removeAttribute("currentUser"); session.invalidate(); // 清除整个会话 } return "redirect:/login?message=" + message; } ``` 请注意,这只是一个简化版的示例,实际生产环境中可能需要考虑更多细节,例如使用Redis等分布式缓存来存储登录状态,以及更复杂的并发控制策略。此外,还要确保服务器配置了适当的会话超时时间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值