2017-02-09 4 views
1

У меня есть приложение весны, которое вводит определенные бобы, укорачивается в зависимости от контекста запроса. В этом примере это фасоль Facebook.Весна Запросить бобы в лямбда

@RestController 
@RequestMapping("facebook") 
public class FacebookInjectionController { 

    @Autowired 
    private Facebook facebook; 

    @Autowired 
    private UserRepository userRepository; 

    @RequestMapping(method = RequestMethod.GET) 
    public List<String> blah() { 
     String firstName = facebook.userOperations().getUserProfile().getFirstName(); 
     return Arrays.asList(firstName); 
    } 

    @RequestMapping(method = RequestMethod.GET, value = "complex") 
    public List<String> blah2() { 
     UserJwt principal = (UserJwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 

     Stream<User> stream = StreamSupport.stream(userRepository.findAll().spliterator(), true); 

     return stream.filter(u -> u.getUid().equals(principal.getUid())) 
       .map(u -> 
         facebook.userOperations().getUserProfile().getFirstName() 
       ).collect(Collectors.toList()); 
    } 

} 

Этот код будет работать нормально, но каждый так часто он потерпит неудачу со следующей ошибкой:

2017-02-09 01:39:59.133 ERROR 40802 --- [o-auto-1-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.facebook': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.] with root cause

java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. 
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) 
    at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41) 
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340) 
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) 
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) 
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187) 
    at com.sun.proxy.$Proxy137.userOperations(Unknown Source) 
    at com.roomsync.FacebookInjectionController.lambda$blah2$5(FacebookInjectionController.java:43) 
    at com.roomsync.FacebookInjectionController$$Lambda$10/2024009478.apply(Unknown Source) 
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 
    at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) 
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374) 
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) 
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) 
    at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:747) 
    at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:721) 
    at java.util.stream.AbstractTask.compute(AbstractTask.java:316) 
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731) 
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) 
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:902) 
    at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1689) 
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1644) 
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) 

я пытался несколько решений (в том числе Spring MVC: How to use a request-scoped bean inside a spawned thread?), но никто из них не работал.

Есть ли способ передать запрос на предметный бобин до лямбды или другой нити?

происходит от того, что https://stackoverflow.com/users/1262865/john16384 сказал я изменил свой конфиг на:

@Bean 
@Scope(value = "inheritableThreadScope", proxyMode = ScopedProxyMode.INTERFACES) 
public ConnectionRepository connectionRepository(ConnectionFactoryLocator connectionFactoryLocator) { 
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 
    if (authentication == null) { 
     throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in"); 
    } 
    return getUsersConnectionRepository(connectionFactoryLocator).createConnectionRepository(authentication.getName()); 
} 

@Bean 
@Scope(value="inheritableThreadScope", proxyMode=ScopedProxyMode.INTERFACES) 
public Facebook facebook(ConnectionFactoryLocator connectionFactoryLocator) { 
    Connection<Facebook> connection = connectionRepository(connectionFactoryLocator).findPrimaryConnection(Facebook.class); 

    return connection != null ? connection.getApi() : null; 
} 

@Bean 
@Scope(value = "inheritableThreadScope", proxyMode = ScopedProxyMode.INTERFACES) 
public ExecutorService fbExecutor() { 
    return Executors.newSingleThreadExecutor(); 
} 

контроллер теперь выглядит следующим образом:

@RestController 
@RequestMapping("facebook") 
public class FacebookInjectionController { 

    @Autowired 
    private Facebook facebook; 

    @Autowired 
    private UserRepository userRepository; 

    @Autowired 
    private ExecutorService fbExecutor; 

    @RequestMapping(method = RequestMethod.GET) 
    public List<String> blah() { 
     String firstName = facebook.userOperations().getUserProfile().getFirstName(); 
     return Arrays.asList(firstName); 
    } 

    @RequestMapping(method = RequestMethod.GET, value = "complex") 
    public List<String> blah2() throws ExecutionException, InterruptedException { 
     UserJwt principal = (UserJwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 

     Stream<User> stream = StreamSupport.stream(userRepository.findAll().spliterator(), true); 

     Future<List<String>> submit = fbExecutor.submit(() -> stream.filter(u -> u.getUid().equals(principal.getUid())) 
       .map(u -> 
         facebook.userOperations().getUserProfile().getFirstName() 
       ) 
       .collect(Collectors.toList())); 

     return submit.get(); 
    } 

} 

я также имеют следующие конфигурации:

@Configuration 
public class BeanFactoryConfig implements BeanFactoryAware { 
    private static final Logger LOGGER = Logger.getLogger(BeanFactoryConfig.class); 

    @Override 
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 
     if (beanFactory instanceof ConfigurableBeanFactory) { 

//   logger.info("MainConfig is backed by a ConfigurableBeanFactory"); 
      ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory; 

      /*Notice: 
      *org.springframework.beans.factory.config.Scope 
      * != 
      *org.springframework.context.annotation.Scope 
      */ 
      org.springframework.beans.factory.config.Scope simpleThreadScope = new SimpleThreadScope() { 
       @Override 
       public void registerDestructionCallback(String name, Runnable callback) { 
             RequestAttributes attributes = RequestContextHolder.currentRequestAttributes(); 
        attributes.registerDestructionCallback(name, callback, 3); 
       } 
      }; 
      cbf.registerScope("inheritableThreadScope", simpleThreadScope); 

      /*why the following? Because "Spring Social" gets the HTTP request's username from 
      *SecurityContextHolder.getContext().getAuthentication() ... and this 
      *by default only has a ThreadLocal strategy... 
      *also see https://stackoverflow.com/a/3468965/923560 
      */ 
      SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL); 

     } 
     else { 
//   logger.info("MainConfig is not backed by a ConfigurableBeanFactory"); 
     } 
    } 
} 

даже при этом он иногда получает ошибку:

{ 
    "timestamp": 1486686875535, 
    "status": 500, 
    "error": "Internal Server Error", 
    "exception": "java.util.concurrent.ExecutionException", 
    "message": "org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.facebook' defined in class path resource [com/roomsync/config/SocialConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.social.facebook.api.Facebook]: Factory method 'facebook' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.connectionRepository': Scope 'inheritableThreadScope' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.", 
    "path": "/facebook/complex" 
} 

так что кажется, что им все еще не хватает кусок, чтобы активировать область и копирование потоков локального контекста к нему

+0

Добавьте ваш web.xml, пожалуйста, – Akshay

+0

Я полагаю, что изменение 'true' на' false' в вызове для создания потока не является вариантом? Вы можете кэшировать значения, которые не зависят от 'u' до операции потока. –

ответ

0

ли это необходимо, что поток обрабатывается параллельно? Это приводит к тому, что лямбда может быть выполнена в другом потоке.

Stream stream = StreamSupport.stream(userRepository.findAll().spliterator(), false);

+0

Я настроил его на истину, потому что это подражает поведению другого контроллера, который будет выполнять лямбда в другом потоке. – arhimmel

0

Там две вещи, идущие на:

1) Java потоки используют общий Fork/Join бассейн выполнить что-то параллельно. Эти потоки не создаются платформой Spring (или вами).

2) Запросить фасонные объекты можно с помощью ThreadLocal.

Это означает, что если нить, не созданная Spring, пытается получить доступ к компоненту с привязкой к запросу, она не будет найдена, поскольку поток не знает об этом (это не в ThreadLocal).

Для того, чтобы решить эту проблему, вам необходимо будет контролировать, какие потоки используются для ваших потоков. Как только вы достигнете этого, вы можете сделать копию бланков с запросом для использования в подпотоках. Вам также нужно будет очистить их снова после того, как поток завершит свою задачу, или вы рискуете оставить бобы позади, что может быть замечено следующей задачей, выполняемой в этом потоке.

Чтобы изменить какие потоки используются параллельными потоками, см: Custom thread pool in Java 8 parallel stream

Как настроить Spring правильно размножать запрос в область действия бобы дочерних потоков вы уже нашли, я думаю.

+0

ive обновил мой вопрос с моей попытки с вашими предложениями и все еще имею проблемы – arhimmel