Spring MVC:如何在每个 Thymeleaf 页面上 re-load 来自数据库的 logged-in 用户信息?

Spring MVC: how can I re-load the logged-in user information from the database on each Thymeleaf page?

我想在我的网站 header 中显示一些关于 logged-in 用户 的信息(因此它们应该在每个包含 header) 的页面。

此信息可能会在数据库中更改,当用户重新加载页面时应该 re-loaded。

我希望有一个全局 object(例如 loggedInUser),当从服务器返回 Thymeleaf 页面时从数据库加载,这样我就可以访问那个 object来自 Thymeleaf 而无需 从每个控制器传递它。

有没有可能达到那样的效果?

**** 更新 ****

例如:我想在 header 栏中为用户显示通知,并且可以通过各种操作将这些通知添加到数据库 server-side 中。当用户加载网站的任何页面(包括 header)时,应加载通知 server-side 并显示在 Thymeleaf 的 header 中。

我想避免加载登录的用户,以便将其传递给服务页面的每个控制器上的 Thymeleaf。

您正在考虑会话作用域 bean。看看Spring Docs。您将在用户登录后创建会话 bean 并在那里存储必要的信息。注入此类作用域 bean 的地方可以检索有关用户的信息。或者将更多数据存储到此缓存中。

但这会使您的应用程序处于全状态,这可能是因为您的应用程序正在水平缩放。如果你在非粘性负载均衡器后面,它就不会工作,因为不能保证来自你用户的后续请求将被路由到会话实例所在的同一节点。

编辑:如果您不想在您的应用程序中有状态,常见的解决方案是在客户端和服务器之间来回发送该数据。但是这种方法会消耗一些带宽。

EDIT1:另一种解决方案是将该信息存储在浏览器 cookie 中。这将在服务器和客户端之间来回发送。

您可以使用拦截器为服务的每个页面向 ModelAndView 实例添加内容。

public class AuthenticationUtil {
    private AuthenticationUtil() {} // private c'tor for utility class

    private static final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();

    /**
     * @return {@code true} if the user is authenticated non-anonymously
     */
    public static boolean isAuthenticated() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication != null &&
            authentication.isAuthenticated() &&
            !authenticationTrustResolver.isAnonymous(authentication);
    }
}

拦截器:

public class NotificationSettingInterceptor extends HandlerInterceptorAdapter {
    @Autowired
    private NotificationRepository notificationRepository;

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if (modelAndView == null) { // can be null for static resources etc.
            return;
        }

        if (AuthenticationUtil.isAuthenticated()) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            modelAndView.addObject("notifications", notificationRepository.findNotificationsFor(authentication.getName()));
        }
    }
}

从这个拦截器创建一个 bean 并注册它,例如通过覆盖您的网站 @Configuration class 的 addInterceptors 方法(扩展 WebMvcConfigurerAdapter

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
    ...
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(notificationSettingInterceptor());
    }
    ...
    @Bean
    public NotificationSettingInterceptor notificationSettingInterceptor() {
        return new NotificationSettingInterceptor();
    }
}

如果使用 xml 命名空间配置,则使用 <mvc:interceptors> 标签:

<mvc:interceptors>
    <bean class="com.example.NotificationSettingInterceptor"/>
</mvc:interceptors>

使用 @ControllerAdvice @ModelAttribute 方法为所有或特定控制器加载数据

@ControllerAdvice(assignableTypes={FooController.class, BarController.class})
public class LoadGlobalData {

  @Autowired
  private MyUserDataService userDataService;

  @ModelAttribute("userData")
  MyUserData getUserData(){
        return userDataService.findByUser(...);
  }  
}

(或者您可以使用处理程序拦截器来完成上述操作)

在您的服务上使用 method caching

@Service
public class MyUserDataService {

  @Cacheable(value="userData", key="#userId")
  public MyUserData findByUser(long userId) {
    ...
  }

  @CacheEvict(value="userData", key="#userId")
  public void bust(long userId) {
    ...
  }
}

使用支持通知的数据库,例如 Oracle 或 PostgreSQL 在数据更改时清除缓存。