Spring MVC:如何在每个 Thymeleaf 页面上重新加载数据库中的登录用户信息?

Posted

技术标签:

【中文标题】Spring MVC:如何在每个 Thymeleaf 页面上重新加载数据库中的登录用户信息?【英文标题】:Spring MVC: how can I re-load the logged-in user information from the database on each Thymeleaf page? 【发布时间】:2015-05-24 04:46:57 【问题描述】:

我想在我的网站标题中显示一些关于已登录用户的信息(因此它们应该在包含标题的每个页面上都可用)。

此信息可以在数据库中更改,并且应在用户重新加载页面时重新加载。

我希望有一个全局对象(例如 loggedInUser),在从服务器返回 Thymeleaf 页面时从数据库加载,这样我就可以从 Thymeleaf 访问该对象从每个控制器传递它。

有没有可能实现这样的目标?

**** 更新 ****

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

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

【问题讨论】:

【参考方案1】:

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

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

  @Autowired
  private MyUserDataService userDataService;

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

(或者你可以为上面使用一个HandlerInterceptor)

在您的服务上使用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,以便在数据更改时破坏您的缓存。

【讨论】:

谢谢,您的解决方案似乎很有希望满足我的需求。我不明白LoadGlobalData 是什么类?.. 有没有办法只建议一些控制器加载我的数据(所以我可以将数据加载限制为仅返回百里香页面的控制器)? @Andrea 在最新的春季 v 中你可以像你问的那样配置 controlleradvice。或者你可以写一个 handlerinterceptor。 太棒了!.. 我使用了@ControllerAdvice(assignableTypes = PagesController.class) ,其中PagesController 是我的控制器类,其方法返回一个Thymeleaf 页面。如果您用您的最后一个建议更新答案将非常适合我的问题。【参考方案2】:

您可以使用拦截器为所服务的每个页面向 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 类的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>

【讨论】:

将是拦截器的代码在每个请求/响应上执行还是仅在响应是 Thymeleaf 页面时执行?.. 我有一些控制器返回的不是 thymeleaf 页面而是提供 JSON,我想避免也在这些控制器上加载用户的通知,因为我不需要。 您可以将拦截器的范围设置为此处描述的范围,例如只处理/**/*.html:***.com/questions/8044277/… 谢谢詹姆斯。我选择@NeilMcGuigan 答案是因为它更简单,并且允许我通过类而不是通过 url 来控制加载用户信息的位置。【参考方案3】:

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

但这会使您的应用程序状态满,这可能是您的应用程序水平扩展的问题。如果您在非粘性负载均衡器后面,它将无法工作,因为不能保证来自您的用户的后续请求将被路由到会话实例所在的同一节点。

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

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

【讨论】:

如果用户信息更改到数据库之后用户登录(并且会话bean已经创建)会发生什么?..是否从数据库刷新信息新请求? 没有您的干预,没有。这是您的应用程序中的状态问题。你通常想避免它。在回答中查看我的“编辑”建议。 没有状态的情况下获取我所问的方法是什么? 见我回答的编辑部分 感谢@lkrnac,但也许我没有很好地解释我的问题..查看我对问题的更新,我添加了一个示例场景,希望能提供更好的解释......我不知道我该怎么做使用 cookie 实现这一点 :)

以上是关于Spring MVC:如何在每个 Thymeleaf 页面上重新加载数据库中的登录用户信息?的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC:如何在每个 Thymeleaf 页面上重新加载数据库中的登录用户信息?

如何在 Spring-mvc 中使用 Session 属性

如何正确计时用 Java Spring MVC 编写的 API?

spring mvc中如何过滤form提交数据中的空格?

如何在 Spring-MVC 中将数据从视图传递到控制器?

Spring MVC权限管理如何设置?