I have situation where I call a feign client. It works fine, until I try putting the feign client inside a thread (simplified version):
@Autowired
UsuarioFeign feignUserClient;
....
final Runnable t = new Runnable() {
@Override
public void run() {
try {
feignUserClient.findByEmail("[email protected]");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
new Thread(t).start();
When I try to do it, I get (printed by the real version, not the simplified one above):
java.lang.NullPointerException
at br.alfa.tutoria.AlfaTutoriaApplication$1.apply(AlfaTutoriaApplication.java:46)
at feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:158)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:88)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)
at com.sun.proxy.$Proxy246.findByEmail(Unknown Source)
at br.alfa.tutoria.service.impl.TutoriaServiceImpl.atribuiFuncaoTutor(TutoriaServiceImpl.java:285)
at br.alfa.tutoria.service.impl.TutoriaServiceImpl$2.run(TutoriaServiceImpl.java:270)
at java.lang.Thread.run(Unknown Source)
The feignUserClient
is @Autowired (I've tried turn it final, and inject it using a constructor, but it made no difference - anyway, it's value is not null when the code execute).
More info:
@FeignClient(name = "authUsers", url = br.alfa.tutoria.config.Url.AUTH_SERVER)
public interface UsuarioFeign {
@RequestMapping(value = "/user-search-by-email", method = RequestMethod.POST)
public User findByEmail(String email);
}
The other class, which calls the feign interface... it doesn't matter. I tried it in several diferent classes (all of them annotated with @RestController
or @Service
). It just stop working if I call the UsuarioFeign.findByEmail
from a thread.
At the end, the problem had to do with Spring Security Context. By design, "the Security is stored in a per Thread basis" according to Spring Security docs. That means that if you need something like the security context in a new thread, you won't get it.
In the docs you can find a way to transfer it as a parameter to a new thread:
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable, context);
new Thread(wrappedRunnable).start();