Из моего опыта:
Плюсы:
- Легко начать
- Динамическая загрузка классов очень мощная
- Если вы реализуете что-то, как показано ниже, вы не сможете долгое время менять сторону сервера и разрабатывать клиент (одно исключение на сервере rmi должно получить эти классы в classpath - так что либо их серверы по сети, либо их включение и перестройка сервера)
Вы можете реализовать два таких интерфейса:
Общий интерфейс задач:
public interface Task<T extends Serializable> extends Serializable {
T execute();
}
Интерфейс Rmi:
public interface RmiTask extends Remote {
<T extends Serializable> T executeTask(Task<T> task) throws RemoteException;
}
RmiTask
реализация на стороне сервера:
public class RmiTaskExecutor implements RmiTask {
public <T extends Serializable> T executeTask(Task<T> task) {
return task.execute();
}
}
Пример клиента Task
реализация:
public class IsFileTask implements Task<Boolean> {
final String path;
public IsFileTask(String path) {
this.path = path;
}
public Boolean execute() {
return new File(path).isFile();
}
}
Минусы:
- Может быть небезопасно, когда используется динамическая загрузка классов (клиент обслуживает реализацию переданных типов) - например, вы знаете, что сервер rmi вызывает
method()
на PassedObject
, но чудесный клиент может переопределить этот метод и выполнить все, что он хочет там ...
- трудно реализовать обратный вызов, который будет работать через Интернет (для этого необходимо установить новое соединение с сервера на клиент - может быть сложно передать его через NAT / маршрутизаторы / брандмауэры)
- когда вы внезапно разорвали соединение во время выполнения удаленного метода, случается, что этот метод не вернется (я рекомендую оборачивать вызовы rmi в
Callable
s и запускать их с определенным временем ожидания).