乐趣区

关于java:在异步方法中获取登陆用户时出现的问题

在重写分配任务算法时须要获取以后登陆用户区域及其子区域,重写之后进行测试的时候发现工作间接停留在了调配中,查看后盾日志时发现了报错

依据报错查看抛出谬误的中央

能够发现是在调用 getAuthUserDetailWithoutTransaction 办法的时候呈现的问题,进一步查看这个办法:

public Optional<AuthUserDetails> getAuthUserDetailWithoutTransaction() {logger.debug("依据认证获取以后登录用户名,并获取该用户");

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication != null) {
      AuthUserDetails userDetail;
      if (authentication instanceof UsernamePasswordAuthenticationToken) {userDetail = (AuthUserDetails) authentication.getPrincipal();} else if (authentication instanceof AuthUserDetails) {userDetail = (AuthUserDetails) authentication;
      } else if (authentication instanceof AnonymousAuthenticationToken) {return Optional.empty();
      } else {throw new RuntimeException("获取类型不正确");
      }
      return Optional.of(userDetail);
    }

    logger.debug("认证用户在数据库中不存在");
    return Optional.empty();}

依据后盾日志输入的 认证用户在数据库中不存在 能够得出 authentication 为 null,而后为了进一步确认我又尝试用 debug 模式运行后盾发现在后盾运行过程中常常会用到下面这个函数,并且获取到的 authentication 都不为 null

只有在分配任务时返回的 authentication 为 null

能够从上面这个简化的图得悉函数调用关系:

又因为报错信息中蕴含

Unexpected exception occurred invoking async method(调用异步办法时发生意外异样)

所以我尝试去掉 addTaskDetailsAndAddFormItemValuesAndUpdateStatistics 办法的异步注解,从新执行后发现一切正常,没有产生报错。
所以就去查问了一下 SecurityContextHolder.getContext()@Async同时应用会产生的问题,发现 @Async 办法中应用 SecurityContextHolder.getContext() 就会返回
null。

测试:

运行后果:

即在异步函数内调用就会返还 null, 并且咱们还能够发现咱们以后的默认线程为 nio-8081-exec-8, 然而异步函数会新建一个 task- 1 线程,并且在新线程中获取不到平安上下文,能够揣测默认状况下平安上下文只会存在于主线程中,它不会随着线程的新建而被传递过去。

SecurityContextHolder 用于存储平安上下文(security context)的信息。以后操作的用户是谁,该用户是否曾经被认证,他领有哪些角色权限…这些都被保留在 SecurityContextHolder 中。SecurityContextHolder 默认应用 ThreadLocal 策略来存储认证信息。这也就意味着,这是一种与线程绑定的策略。Spring Security 在用户登录时主动绑定认证信息到以后线程,在用户退出时,主动革除以后线程的认证信息。

如果咱们在 getAuthUserDetailWithoutTransaction 中这样设置平安上下文策略便可解决此问题,然而这样的话又可能对调用此办法的其余办法造成影响,所以咱们不得不寻找新的解决办法。

 public Optional<AuthUserDetails> getAuthUserDetailWithoutTransaction() {logger.debug("依据认证获取以后登录用户名,并获取该用户");
    // 设置可继承本地线程策略,使得其能够在异步办法中被调用
    SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

    . . .
}

起初我想的是只须要在这个异步办法中设置策略即可,然而测试后发现此策略并不会随着办法的调用而被传递过去。
于是我尝试间接在 M 层获取到以后登陆用户再将登陆用户传给异步办法

然而再次尝试时又产生了新的报错

failed to lazily initialize a collection of role: club.yunzhi.smartcommunity.entity.District.children, could not initialize proxy - no Session

搜寻后发现 Hibernate 再进行关联解决时通常用懒加载,能够防止大量数据屡次传递,也就是说咱们在 Aservice 里获取到了 user,user 对应的区域的子区域能够在 Aservice 中调用,然而如果把 user 又传给 Bservice, 那么再想调用 user 对应的区域的子区域就会产生报错。
源 service:

承受传递的 service:

也就是说咱们在进行传递参数时为了防止这种状况的产生能够间接传递相应的 id,再依据 ID 通过仓库层获取咱们的数据。
也就是说咱们能够改成如下这种形式达到目标

 public void taskAssign(){
   .   .   .   
 Optional<Long> optionalWebUserId = this.webUserService.getCurrentLoginWebUserId();
 Long webUserId = optionalWebUserId.orElse(null);
 // 异步生成工作详情与表单,并更新各区域统计状况。this.taskDetailAsyncService.addTaskDetailsAndAddFormItemValuesAndUpdateStatistics(task, residents, specification, webUserId);
}
List<District> getManageDistrictsWithCurrentLoginUser(Long ...webUserId);
  public final List<District> getManageDistrictsWithCurrentLoginUser(Long ...webUserId) {
    List<District> result;
    Optional<WebUser> optionalUser;

    if(webUserId.length == 0) {optionalUser =  this.webUserService.getCurrentLoginWebUser();
    } else {optionalUser = this.webUserRepository.findById(webUserId[0]);
   }
    . . .

    }

此时再次尝试便可达到咱们想要的成果。

另外再说一下如何展现多选或单选组件,咱们想要达到的成果:

并且用户点击选项 2 也不会扭转显示,即相似于 disable 的成果,然而如果咱们间接申明 input 为 disable 就会像上面这样显示得不是很清晰。

如果咱们常应用 redonly 的话会发现点击后还是会给予相应的显示只是不会扭转 value 值。
解决办法:input 中申明 onclick 为 return false

<input
       .  .  .
       onclick = "return false"
       type="radio" >

问题三:
我的项目中如果一个人有多处房产,那么分配任务后再进行预览就会发现多个记录

遇到这种状况我首先想到的就是分配任务时生成数据生成反复了,然而看完调配代码并没有发现任何问题,在数据库中查看也没有生成反复的数据项。也确认了前调用的端口与后盾相匹配,于是就尝试在前台输入一下 page,后果发现返回来的数据截然不同,同样也在后盾查看了返还给前台的数据发现时返还了三个雷同的数据,对应的 id 也是雷同的。

到这里就本就能够确认是在查问上出了问题。
然而咱们在前台没有传递任何查问参数,所以根本能够确认是后盾在依据用户区域进行查问时出了问题。
后盾查问条件是这样结构的

Join<TaskDetail, Building> buildingJoin = root.join("resident")
          .join("houses", JoinType.LEFT)
          .join("building", JoinType.LEFT);
switch (district.getType()) {
case TYPE_BUILDING:
          return criteriaBuilder.equal(buildingJoin.get("id").as(Long.class), districtId);
 . . .
}


也就是说每个查问都是独立的,好呢局 house1 查问完返还一个数据,再依据其余 house 查问再进行返还,并不是我设想中的下图的形式:

如果咱们想要去除这些反复项只需在查问条件里新增这样一条即可:

 public static Specification<TaskDetail> distinct() {return (root, criteriaQuery, criteriaBuilder) -> {criteriaQuery.distinct(true);
      return criteriaQuery.getRestriction();};
  }
退出移动版