为什么一次呼叫可观察功能执行两次?

时间:2018-11-27 11:18:50

标签: java-8 rx-java

程序的完整结构

注释:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UserAnnotation {
}

然后创建了一个拦截器:

public class UserInterceptor implements MethodInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(UserInterceptor.class);

    @Inject
    UserService userService; // this is not working

    public Object invoke(MethodInvocation invocation) throws Throwable {
        logger.info("UserInterceptor : Interceptor Invoked");
        Object result = invocation.proceed();
        Observable<List<User>> observable = (Observable<List<Sample>>) result;
        observable.flatMap(Observable::from).subscribe(object -> {
            User user = (User)object
            SampleSender sender = new SampleSender();
            sender.setBoolean(user.isBoolean());
            logger.info("Pushing Data into Sender");
            userService.insert(String.join("_", "key", "value"), sender); 
        }
        return result;
    }
}

然后我创建了一个GuiceModule,如下所示:-

public class UserModule extends AbstractModule {
    @Override
    protected void configure() {
        SampleInterceptor interceptor = new SampleInterceptor()
        requestInjection(interceptor);
        bindInterceptor(Matchers.any(), Matchers.annotatedWith(SampleAnnotation.class), interceptor);
}

}

我正在使用上述注释的类是

// This class also have so many method and this was already declared and using in another services, I created a sample class here
class UserClassForInterceptor {

      @Inject
      AnotherClass anotherClass;
      // this userMethod() is not a new method, its already created, 
      // now I am adding annotation to it, because after finishing this functionality, 
      // I want something should be done, so created annotation and added here
      @UserAnnotation
      public Observable<List<Sample>> userMethod() {
            logger.info("This is printing only once");
            return anotherClass.getUser().flatMap(user ->{
                logger.info("This is also printing twice");
                // this logger printed twise means, this code snippet is getting executed twise
            });
      }
}

public class AnotherClass{
          public Observable<User> getUser(){
           Observable<Sample> observableSample = methodReturnsObservableSample();
           logger.info("Getting this logger only once");
           return observableSample.map(response-> {
               logger.info("This logger is printing twice");
               //here have code to return observable of User
           });
      }
}

如果我删除可观察对象内部的注释记录器,则仅打印一次,但是当我使用注释时,这些记录器将按时打印。为什么我不知道这样的行为。

我有一个RestModule,使用它绑定UserClassForInterceptor如下

public final class RestModule extends JerseyServletModule {
    // other classes binding
    bind(UserClassForInterceptor.class).in(Scopes.SINGLETON);
    // other classes binding
    install(new SampleModule());
}

现在我有一个bootsrap类,其中绑定了RestModule

public class Bootstrap extends ServerBootstrap {
   binder.install(new RestModule());
}

用法:-

@Path("service/sample")
public class SampleRS {
    @Inject
    UserClassForInterceptor userClassForInterceptor;

    public void someMethod() {
        userClassForInterceptor.sampleMethod();
    }
}

2 个答案:

答案 0 :(得分:1)

您创建了一个注释@UserAnnotation,以及一个与该注释一起使用的拦截器类。您将注释附加到方法userMethod()

拦截器例程所做的第一件事是调用userMethod()以获取它返回的观察值,然后拦截器订阅到返回的观察值,从而导致出现第一条日志消息。最终,拦截器将可观察对象返回给原始调用者。当其他人订阅了返回的observable时,观察者链又被激活,因此日志消息出现两次。

RxJava有副作用

尽管RxJava是“功能性反应式编程”概念的实现,但您构造(以功能性方式)构造的观察者链仅在被订阅时才起作用,并且这些订阅具有副作用。记录输出是一个副作用,可能是最有益的。变量的更改或具有副作用的方法的调用具有更广泛的影响。

(适当地)构造观察者链时,它将充当潜在的计算,直到有订阅者为止。如果您需要拥有多个订阅者(对于您的问题域可能是这样),则必须决定是否需要为每个订阅(正常情况)激活观察者链,或者对于所有重叠的订阅只激活一次。

如果希望所有重叠的订阅共享相同的可观察对象,则可以使用share()运算符。有许多相关的运算符会影响可观察项和订阅的生存期。以下是概述:How to use RxJava share() operator?

面向方面的编程:拦截器和Guice

您的代码正在使用Guice提供一种称为“面向方面的编程”的功能。这使您可以将代码引入程序中,以解决跨领域的问题,或者通过设置受控网关来增强其功能。使用Guice或类似的AOP方法需要纪律

在您的情况下,您通过订阅具有非平凡副作用的观察者链,使用了拦截过程来导致无法解释的(直到现在)副作用。想象一下,您截获的方法建立了一个一次性连接,而拦截器用尽了该连接以完成其工作,而原始调用方则无法使用该连接。

您需要的纪律是了解拦截器必须遵循的规则。想想诸如“首先,不要伤害”之类的规则。

以FRP方式做事

如果在处理用户信息时需要添加一个额外的步骤,则应该在拦截器中构造一个新的可观察对象,该操作必须做到这一点,但前提是原始调用方订阅了该可观察对象:

    Object result = invocation.proceed();
    Observable<List<User>> observable = (Observable<List<Sample>>) result;
    Observable<List<User>> newObservable = observable
      .doOnNext( sampleList ->
         Observable.fromIterable( sampleList )
           .subscribe(object -> {
             User user = (User)object
             SampleSender sender = new SampleSender();
             sender.setBoolean(user.isBoolean());
             logger.info("Pushing Data into Sender");
             userService.insert(String.join("_", "key", "value"), sender); 
           }));
    return newObservable;

通过返回修改后的观察者链,您不会引入原始观察者链的副作用,并确保您在自己的代码中引入的副作用将被触发链已订阅。

答案 1 :(得分:0)

这段代码对我也有帮助

public Object invoke(MethodInvocation invocation) throws Throwable {
    Object result = null;
    try{
        logger.debug("Interceptor Invoked");
        result = invocation.proceed();
        Observable<List<User>> observable = (Observable<List<User>>)result;

        return observable
                .doOnNext(this::updateUser);
    }
    catch(Exception ex){
        logger.error("Error: ",ex);
    }
    return result;
}

private void updateUser(List<User> users) {
    if(CollectionUtils.isNotEmpty(users)) {
        for(User user: users) {
            SampleSender sender = new SampleSender();
            sender.setBoolean(user.isBoolean());
            logger.info("Pushing Data into Sender");
            userService.insert(String.join("_", "key", "value"), sender); 
        }
    }
}