我想知道如何确保每个会话一次只访问一次服务中的某个方法。
我将通过一个小例子说明:
假设我们的用户处于状态A(user.state = A)。此用户向我们的java spring控制器发送HTTP GET请求以获取页面,例如/ hello。根据他的身份,他将被送到A或B.在此之前,我们将他的状态改为B(见下面的代码)。
现在,再次假设调用dao.doSomething();花了很多时间。如果用户发送另一个GET(例如通过刷新他的浏览器),他将调用完全相同的方法dao.doSomething(),从而产生2个调用。
你怎么能避免这种情况?
如果您同时发送2个HTTP GET会怎样?
如何在控制器/服务/型号/数据库中保持一致?
注1:这里我们不从不同的浏览器发出2个HTTP GET。我们只是在同一个浏览器上同时创建它们(我知道最大并发会话解决方案,但这并不能解决我的问题。)。
注2:解决方案不应阻止控制器对不同用户的并发访问。
我已经阅读了一些有关服务交易的内容,但我不确定这是否是解决方案。我也读过一些关于并发的内容,但我仍然不明白如何在这里使用它。
非常感谢您的帮助!谢谢!
代码示例:
@Controller
public class UserController {
@RequestMapping(value='/hello')
public String viewHelloPage() {
// we get the user from a session attribute
if (user.getState() = A) {
user.setStatus(B);
return "pageA";
}
return "pageB";
}
@Service
public class UserService {
Dao dao;
@Override
public void setStatus(User user) {
dao.doSomething();
user.setStatus(B);
}
}
答案 0 :(得分:1)
虽然我不推荐它(因为它基本上阻止来自同一用户的所有其他呼叫)。在大多数HandlerAdapter
实现中,您可以默认设置属性synchronizeOnSession
,这是false
,允许并发请求来自同一客户端。当您将此属性设置为true
时,请求将排队等候该客户端。
如何设置取决于HandlerAdapter
的配置。
答案 1 :(得分:0)
如果dao.doSomething()
正在做你想要发生一次的工作,你应该使用像PUT或DELETE这样的幂等方法。没有法律强制您使用正确的方法,但最糟糕的情况是,它是一种自我记录的方式来告诉全世界您应该如何使用API。如果这还不够,大多数浏览器会尝试根据请求的类型帮助您。例如,浏览器通常会使用缓存来避免多个GET。
您真正想知道的是如何强制执行幂等性。这是特定于应用程序的。一种通用方法是在服务器端生成并存储伪唯一ID,以便客户端附加到其请求。这样,可以安全地忽略在第一个之后具有相同id的任何请求。显然,旧的ID应该被明智地驱逐。
正如我所说,解决方案通常是特定于应用程序的。在上面的例子中,看起来你正试图在两种状态之间切换,而你的实现是服务器端切换。您可以利用客户端来确保多个请求不会成为问题。
@RequestMapping(value="/hello", method=RequestMethod.PUT)
public String test(@RequestParam("state") String state) {
dao.setState(user, state)
switch (state) {
case "A":
return "B";
case "B":
return "A";
default:
return "error";
}
}
答案 2 :(得分:0)
如何确保服务中的某些方法只被访问一次 每次会议的时间。
在调用服务方法
之前,尝试锁定控制器中的会话对象答案 3 :(得分:0)
如果您不介意配置和使用AOP,那么以下内容可能对您有所帮助
@Aspect
@Component
public class NonConcurrentAspect implements HttpSessionListener{
private Map<HttpSession, Map<Method, Object>> mutexes = new ConcurrentHashMap<HttpSession, Map<Method, Object>>();
@Around(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object handle(ProceedingJoinPoint pjp) throws Throwable {
MethodInvocationProceedingJoinPoint methodPjp = (MethodInvocationProceedingJoinPoint) pjp;
Method method = ((MethodSignature) methodPjp.getSignature()).getMethod();
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
HttpSession session = request.getSession(false);
Object mutex = getMutex(session, method);
synchronized (mutex) {
return pjp.proceed();
}
}
private Object getMutex(HttpSession session, Method method) {
Map<Method, Object> sessionMutexes = mutexes.get(session);
Object mutex = new Object();
Object existingMutex = sessionMutexes.putIfAbsent(method, mutex);
return existingMutex == null ? mutex : existingMutex;
}
@Override
public void sessionCreated(HttpSessionEvent se) {
mutexes.put(se.getSession(), new ConcurrentHashMap<Method, Object>());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
mutexes.remove(se.getSession());
}
}
它在每个会话的每个方法互斥锁上进行同步。一个限制是所建议的方法不应该互相调用(除非你严重违反MVC设计模式,否则几乎不会这样),否则你可能会遇到死锁。
这会处理用@RequestMapping
标记的所有方法,但是如果你想要防止并发执行的几个方法,
那么,作为可能的解决方案之一,您可以引入自己的注释,例如
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NonConcurrent {
}
使用此批注标记特定方法,并将上述方面类中的@RequestMapping
批注中的@Around
替换为您自己的方法。
在高度竞争的环境中,您可能会想到比内在锁更高级的解决方案。
但是,我建议不要使用HandlerAdapter
的{{1}}选项,这不仅是因为它同步了同一个互斥锁上的所有调用,而且不太明显,公开可用的同步互斥是有潜在危险的。