Вы достигли нового уровня
— Привет, Амиго!
— Привет, Билаабо! Как жизнь?
— Отлично. Вчера выводил паразитов, но пока не очень-то получается. А потом опять пришлось ночевать в мусорном баке.
— Т.е. все по-прежнему отлично?
— Можно и так сказать.
— Гуд. А что у нас будет сегодня?
— Сегодня я тебе расскажу про класс RandomAccessFile.
Дело в том, что FileInputStream и FileOutputStream представляют файлы в виде потоков: читать из них и писать в них можно только последовательно.
Это не всегда очень-то удобно. Иногда тебе нужно записать пару строк в середину файла, или прочитать пару страниц текста в конце многомегабайтного файла. Читать для этого весь файл не очень эффективно.
Для решения этой проблемы был создан класс RandomAccessFile. С его помощью можно писать в любое место файла, читать из него, а также писать и читать файл одновременно.
— Как интересно.
— Ага. Очень удобно на самом деле.
— А как читать из произвольного места?
— Все довольно просто. Представь, что у тебя открыт текстовый редактор «блокнот». В нем есть курсор. Когда ты что-то печатаешь, текст добавляется в том месте, где он стоит. С чтением файла то же самое. Чтение происходит в том месте, где стоит «курсор». При чтении/записи он сам автоматически сдвигается.
Давай, я лучше покажу тебе пример:
Чтение файла: |
---|
//r- read, файл открыт только для чтения RandomAccessFile raf = new RandomAccessFile(«input.txt», «r»); //перемещаем «курсор» на 100-й символ. //читаем строку, начиная с текущего положения курсора и до конца строки //закрываем файл |
В этом примере я хотел бы обратить твое внимание на две вещи:
Во-первых, создание объекта RandomAccessFile. Вторым параметром идет буква r. Это означает, что файл открыт для чтения (r— read). Если ты хочешь открыть файл для чтения и записи, в конструктор надо передать “rw” вместо “r”.
Во-вторых, обрати внимание на метод seek. С помощью этого метода можно прыгать по файлу и менять позицию курсора для текущей операции чтения/записи. Сразу при открытии файла «курсор» устанавливается на 0-й байт. Или точнее – перед нулевым байтом.
— Правильно ли я понял, что мы открыли файл, и курсор был в его самом начале – на позиции 0. Затем мы вызвали seek и он переместился на 100-й байт. А когда вызвали readLine, то чтение уже было начиная с сотого байта. Так?
— Да. Только хочу обратить твое внимание на то, что метод seek позволяет произвольно прыгать по файлу. Пример:
Чтение файла: |
---|
//r- read, файл открыт только для чтения RandomAccessFile raf = new RandomAccessFile(«input.txt», «r»); // «курсор» стоит на 0-м символе. String text1 = raf.readLine(); //перемещаем «курсор» на 100-й символ. //перемещаем «курсор» на 0-й символ. //закрываем файл |
В данном примере мы вначале прочитали строку, начиная с 0-го байта. Затем прыгнули на сотый байт и прочитали строку там. Затем снова прыгнули на 0-й байт и прочитали строку. Т.е. text1 и text3 – это идентичные строки.
— Ага. Ситуация начинает проясняться.
— Отлично. Тогда вот тебе еще один пример:
Чтение файла: |
---|
//rw- read/write, файл открыт и для чтения и для записи RandomAccessFile raf = new RandomAccessFile(«seek.txt», «rw»); //пишем в файл строку, начиная с 0-го байта //ставим курсор на 8-й символ //закрываем файл |
Тут мы открываем файл для чтения и записи – в конструктор передаем «rw» (read/write).
Затем пишем в файл строку «It is a string».
Затем переставляем курсор на 8-й байт (как раз на начало слова string)
Затем пишем в файл строку «surprise!»
В результате файл будет содержать «It is a surprise!»
— Т.е. байты не вставляются в середину файла, а заменяют те, которые там были?
— Ага.
— А если мы установим курсор в самый конец файла?
— Тогда байты будут писаться в конец, а файл – удлиняться. Т.е. практически то же самое, когда ты пишешь текст в текстовом редакторе.
— Гм. Вроде все понятно. А можно полный список методов класса RandomAccessFile?
— Конечно. Держи:
Метод | Описание |
---|---|
int read() | Читает один байт и возвращает его |
int read(byte b[], int off, int len) | Читает массив байт |
int read(byte b[]) | Читает массив байт |
void readFully(byte b[]) | Читает массив байт, ждет, пока добавятся новые байты, если их не хватает для заполнения массива |
int skipBytes(int n) | Пропускает n байт. Т.е. перемещает курсор на n байт вперед. |
void write(int b) | Пишет один байт в то место, где стоит курсор |
void write(byte b[]) | Пишет массив байт в то место, где стоит курсор |
void write(byte b[], int off, int len) | Пишет массив байт в то место, где стоит курсор |
long getFilePointer() | Возвращает номер байта, на который указывает «курсор». Может быть от 0 до «длины файла» |
void seek(long pos) | Перемещает «курсор», используемый для чтения/записи, в указанное место |
long length() | Возвращает длину файла |
void setLength(long newLength) | Устанавливает новую длину файла. Если файл был больше – он обрезается, если меньше – расширяется и новое место заполняется нулями |
void close() | Закрывает файл |
boolean readBoolean() | Читает boolean с текущей позиции курсора в файле |
byte readByte() | Читает byte с текущей позиции курсора в файле |
char readChar() | Читает char с текущей позиции курсора в файле |
int readInt() | Читает int с текущей позиции курсора в файле |
long readLong() | Читает long с текущей позиции курсора в файле |
float readFloat() | Читает float с текущей позиции курсора в файле |
double readDouble() | Читает double с текущей позиции курсора в файле |
String readLine() | Читает строку из файла и возвращает ее |
void writeBoolean(boolean v) | Пишет boolean в файл (начиная с позиции курсора) |
void writeByte(int v) t | Пишет byte в файл (начиная с позиции курсора) |
void writeChar(int v) | Пишет char в файл (начиная с позиции курсора) |
void writeInt(int v) | Пишет int в файл (начиная с позиции курсора) |
void writeLong(long v) | Пишет long в файл (начиная с позиции курсора) |
void writeFloat(float v) | Пишет float в файл (начиная с позиции курсора) |
void writeDouble(double v) | Пишет double в файл (начиная с позиции курсора) |
void writeBytes(String s) | Пишет строку в файл (начиная с позиции курсора) |
void writeChars(String s) | Пишет строку в файл (начиная с позиции курсора) |
— Гм. Ничего принципиально нового. Разве что пара методов seek()/getFilePointer() и length()/setLength().
— Да, Амиго. Все примерно то же самое. Но ведь удобно?
— Удобно. Спасибо тебе, Билаабо, за интересную лекцию и за те примеры, что ты мне дал.
— Рад помочь, друг Амиго!
— Привет, Амиго!
Задачи |
---|
1. Запись в файл
В метод main приходят три параметра: |
— Привет, Амиго!
— Привет, Элли!
— Сегодня я хочу тебе рассказать про классы StringReader и StringWriter. Принципиально нового тут для тебя будет мало, но иногда эти классы бывают очень полезны. И, как минимум, я хочу, чтобы ты знал, что они есть.
Эти классы – это простейшие реализации абстрактных классов Reader и Writer. И практически аналоги FileReader и FileWriter. Но, в отличие от них, они работают не с данными в файле на диске, а со строкой (String) находящейся в памяти Java-машины.
— А зачем нужные такие классы?
— Иногда нужны. StringReader – это, фактически, переходник между классом String и Reader. А StringWriter – это строка, которая унаследована от Writer. М-да. Сама вижу, что объяснение не очень. Давай лучше для начала рассмотрим пару примеров.
Например, ты хочешь проверить, как работает твой метод, который должен вычитывать данные из переданного в него объекта Reader. Вот как это можно сделать:
Чтение из объекта reader: |
---|
public static void main (String[] args) throws Exception { String test = «Hi!\n My name is Richard\n I’m a photographer\n»; //это строчка – ключевая: мы «превратили» строку в Reader executor(reader); public static void executor(Reader reader) throws Exception |
— Т.е. мы просто взяли строку, обернули ее в StringReader и передали вместо объекта Reader? И из нее все будет читаться, как и надо?
— Ага. Гм. А в этом есть смысл. А теперь проверим, как работают методы StringWriter. Для этого усложним пример. Теперь он будет не просто читать строки, и выводить их на экран, а разворачивать их задом наперед и выводить в объект writer. Пример:
Чтение из объекта reader и запись в объект writer: |
---|
public static void main (String[] args) throws Exception { //эту строку должен будет прочитать Reader String test = «Hi!\n My name is Richard\n I’m a photographer\n»; //заворачиваем строку в StringReader StringReader reader = new StringReader(test); //Создаем объект StringWriter //переписываем строки из Reader во Writer, предварительно развернув их //получаем текст, который был записан во Writer //выводем полученный из Writer’а текст на экран public static void executor(Reader reader, Writer writer) throws Exception //разворачиваем строку задом наперед //пишем строку в Writer |
Мы создали объект StringWriter, внутри которого есть строка, в которой хранится все, что в этот writer пишут. А чтобы ее получить, надо всего лишь вызвать метод toString().
— Гм. Как-то все слишком просто получается. Метод executor работает с объектами потокового ввода reader и writer, а в методе main мы работаем уже со строками.
Все действительно так просто?
— Ага. Чтобы преобразовать строку в Reader достаточно написать:
Создание Reader из String |
---|
String s = «data»; Reader reader = new StringReader(s); |
А преобразовать StringWriter к строке еще проще:
Получение String из Writer |
---|
Writer writer = new StringWriter(); /*тут пишем кучу данных во writer */ String result = writer.toString(); |
— Отличные классы, как по мне. Спасибо за рассказ, Элли.
Задачи |
---|
1. Пишем стек-трейс
Реализуйте логику метода getStackTrace, который в виде одной строки должен возвращать весь стек-трейс переданного исключения. |
2. Читаем из потока
Реализуйте логику метода getAllDataFromInputStream. Он должен вернуть StringWriter, содержащий все данные из переданного потока. |
— И это снова я.
— Привет, Элли!
— Сегодня я хочу тебе подробно рассказать про BufferedReader и BufferedWriter.
— Так ты мне уже рассказывала все про них. Ну ничего там сложного нет.
— Ок. Расскажи, как работает BufferedReader.
— BufferedReader — это как переходник в розетке с 110 к 220 вольт.
В конструктор объекта BufferedReader обязательно нужно передать объект Reader, из которого он будет читать данные. Объект BufferedReader читает из Reader’а данные большими кусками и хранит их у себя внутри в буфере. Поэтому чтение из пары BufferedReader+Reader быстрее, чем прямо из Reader.
— Верно. А BufferedWriter?
— Тут тоже все просто. Когда мы пишем в FileWriter, например, то данные сразу записываются на диск. Если мы часто пишем небольшие данные, то происходит много обращений к диску, что замедляет работу программы. А если мы используем BufferedWriter в качестве «переходника», то операция записи на диск ускорится. BufferedWriter, при записи в него, сохраняет переданные данные во внутреннем буфере, а когда буфер заполняется – пишет данные во Writer одним большим куском. Это гораздо быстрее.
— Гм. Все верно. А что ты забыл?
— После окончания записи у объекта BufferedReader надо вызвать метод flush(), чтобы он записал данные из буфера во Writer, которые еще не записаны, т.е. буфер не заполнен до конца.
— А кроме того?
— А кроме того, пока буфер еще не записан во Writer, данные можно удалить и/или заменить на другие.
— Амиго! Я поражена! Да ты просто эксперт. Ладно, тогда я расскажу тебе о новых классах: ByteArrayStream, PrintStream.
Итак, ByteArrayInputStream и ByteArrayOutputStream.
Эти классы по сути чем-то похожи на StringReader и StringWriter. Только StringReader читал символы (char) из строки (String), а InputStream читает байты из массива байт (ByteArray).
StringWriter писал символы (char) в строку, а ByteArrayOutputStream пишет байты в массив байт у него внутри. При записи в StringWriter строка внутри него удлинялась, а при записи в ByteArrayOutputStream его внутренний массив байт тоже динамически расширяется.
Вспомни пример, который давали тебе на прошлой лекции:
Чтение из объекта reader и запись в объект writer: |
---|
public static void main (String[] args) throws Exception { String test = «Hi!\n My name is Richard\n I’m a photographer\n»; StringReader reader = new StringReader(test); StringWriter writer = new StringWriter(); executor(reader, writer); String result = writer.toString(); System.out.println(«Результат: «+result); public static void executor(Reader reader, Writer writer) throws Exception StringBuilder sb = new StringBuilder(line); writer.write(newLine); |
Вот как он будет выглядеть, если тут работать не с символами, а с байтами:
Чтение из объекта InputStream и запись в объект OutputStream: |
---|
public static void main (String[] args) throws Exception { String test = «Hi!\n My name is Richard\n I’m a photographer\n»; InputStream inputStream = new ByteArrayInputStream(test.getBytes()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); executor(inputStream, outputStream); String result = new String(outputStream.toByteArray()); public static void executor(InputStream inputStream, OutputStream outputStream) |
Тут все аналогично примеру выше. Вместо String – ByteArray. Вместо Reader – InputStream, вместо Writer – OutputStream.
Единственные еще два момента – это преобразование строки в массив байт и обратно. Как ты видишь, это делается довольно несложно:
Преобразование строки в массив байт и обратно |
---|
public static void main (String[] args) throws Exception { String test = «Hi!\n My name is Richard\n I’m a photographer\n»; byte[] array = test.getBytes(); String result = new String(array); |
Чтобы получить байты, которые уже добавлены в ByteArrayInputStream, надо вызвать метод toByteArray().
— Ага. Аналогия с StringReader/StringWriter довольно сильная, особенно когда ты мне ее показала. Спасибо, Элли, действительно интересный урок.
— Куда это ты спешишь? У меня есть еще небольшой подарок – хочу рассказать тебе про класс PrintStream.
— PrintStream? В первый раз слышу о таком классе.
— Ага. Особенно, если не считать, что ты им пользуешься с первого дня, когда ты начал изучать Java. Помнишь System.out? так вот – System.out – это статическая переменная класса System типа… PrintStream! Именно оттуда растут ноги всех этих print, println и т.д.
— Ого. Как интересно. Я как-то ни разу и не задумывался. Расскажи подробнее.
— Гуд. Тогда слушай. Класс PrintStream был придуман для читабельного вывода информации. Он практически весь состоит из методов print и println. См. таблицу:
Методы | Методы |
---|---|
void print(boolean b) | void println(boolean b) |
void print(char c) | void println(char c) |
void print(int c) | void println(int c) |
void print(long c) | void println(long c) |
void print(float c) | void println(float c) |
void print(double c) | void println(double c) |
void print(char[] c) | void println(char[] c) |
void print(String c) | void println(String c) |
void print(Object obj) | void println(Object obj) |
void println() | |
PrintStream format (String format, Object … args) | |
PrintStream format (Locale l, String format, Object … args) |
Также есть несколько методов format, чтобы можно было выводить данные на основе шаблона. Пример:
Преобразование строки в массив байт и обратно |
---|
String name = «Kolan»; int age = 25; System.out.format(«My name is %s. My age is %d.», name, age); |
Вывод на экран: |
My name is Kolan. My age is 25. |
— Ага, помню, мы уже когда-то разбирали метод format у класса String.
— На этом все.
— Спасибо, Элли.
— Хе-хе, Амиго. Смотри, что я для тебя придумал:
Задачи |
---|
1. Генератор паролей
Реализуйте логику метода getPassword, который должен возвращать ByteArrayOutputStream, в котором будут байты пароля. |
— Привет, Амиго.
— Здорово, Риша.
— Сегодня я расскажу тебе новую и очень интересную тему – динамические прокси.
В Java есть несколько способов изменить функциональность нужного класса…
Способ первый — наследование.
Самый простой способ изменить поведение некоторого класса – это создать новый класс, унаследовать его от оригинального (базового) и переопределить его методы. Затем, вместо объектов оригинального класса использовать объекты класса наследника. Пример:
Reader reader = new UserCustomReader(); |
Способ второй – использование класса-обертки(Wrapper).
Примером такого класса есть BufferedReader. Во-первых, он унаследован от Reader, т.е. может быть использован вместо него. Во-вторых, он переадресует все вызовы к оригинальному объекту Reader, который обязательно нужно передать в конструкторе объекту BufferedReader. Пример:
Reader readerOriginal = new UserCustomReader(); Reader reader = new BufferedReader(readerOriginal); |
Способ третий – создание динамического прокси (Proxy).
В Java есть специальный класс (java.lang.reflect.Proxy), с помощью которого фактически можно сконструировать объект во время исполнения программы (динамически), не создавая для него отдельного класса.
Это делается очень просто:
Reader reader = (Reader)Proxy.newProxyInstance(); |
— А вот это уже что-то новенькое!
— Но, нам ведь не нужен просто объект без методов. Надо чтобы у этого объекта были методы, и они делали то, что нам нужно. Для этого в Java используется специальный интерфейс InvocationHandler, с помощью которого можно перехватывать все вызовы методов, обращенные к proxy-объекту. proxy-объект можно создать только используя интерфейсы.
Invoke – стандартное название для метода/класса, основная задача которого просто вызвать какой-то метод.
Handler – стандартное название для класса, который обрабатывает какое-то событие. Например, обработчик клика мышки будет называться MouseClickHandler, и т.д.
У интерфейса InvocationHandler есть единственный метод invoke, в который направляются все вызовы, обращенные к proxy-объекту. Пример:
Пример:
Код |
---|
Reader reader = (Reader)Proxy.newProxyInstance(new CustomInvocationHandler()); reader.close(); |
class CustomInvocationHandler implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(«yes!»); return null; } } |
При вызове метода reader.close(), вызовется метод invoke, и на экран будет выведена надпись “yes!”
— Т.е. мы объявили класс CustomInvocationHandler, в нем реализовали интерфейс InvocationHandler и его метод invoke. Метод invoke при вызове выводит на экран строку “yes!”- Затем мы создали объект типа CustomInvocationHandler и передали его в метод newProxyInstance при создании объекта-proxy.
— Да, все верно.
Это очень мощный инструмент, обычно создание таких прокси используется для имитации объектов из программ, которые физически запущены на другом компьютере. Или для контроля доступа – в таком методе можно проверять права текущего пользователя, обрабатывать ошибки, логировать ошибки и многое другое.
Вот пример, где метод invoke еще и вызывает методы оригинального объекта:
Код |
---|
Reader original = new UserCustomReader();
Reader reader = (Reader)Proxy.newProxyInstance(new CustomInvocationHandler(original)); |
class CustomInvocationHandler implements InvocationHandler { private Reader readerOriginal; CustomInvocationHandler(Reader readerOriginal) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable // это вызов метода close у объекта readerOriginal |
В данном примере есть две особенности.
Во-первых, в конструктор передается «оригинальный» объект Reader, ссылка на который сохраняется внутри CustomInvocationHandler.
Во-вторых, в методе invoke мы снова вызываем этот же метод, но уже у «оригинального» объекта.
— Ага. Т.е. вот эта последняя строчка и есть вызов того же самого метода, но уже у оригинального объекта:
return method.invoke(readerOriginal, args); |
— Ага.
— Не сказал бы, что слишком очевидно, но все же понятно. Вроде бы.
— Отлично. Тогда вот еще что. В метод newProxyInstance нужно передавать еще немного служебной информации для создания proxy-объекта. Но, т.к. мы не создаем монструозные прокси-объекты, то эту информацию легко получить из самого оригинального класса.
Вот тебе пример:
Код |
---|
Reader original = new UserCustomReader();
ClassLoader classLoader = original.getClass().getClassLoader(); Reader reader = (Reader)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); |
class CustomInvocationHandler implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } } |
— Ага. ClassLoader и список интерфейсов. Это что-то из Reflection, да?
— Ага.
— Ясно. Что ж, думаю, я смогу создать примитивный простенький прокси объект, если это когда-нибудь мне понадобится.
— А ты спроси у Диего
— Привет, Амиго! Я придумал тебе пару интересных задач.
Решать их можно только в Intellij IDEA. Вот, смотри, какие интересные условия…
Задачи |
---|
1. Создание прокси-объекта
1) В отдельном файле создать публичный класс CustomInvocationHandler, который будет хэндлером при создании прокси-объекта. |
2. Дженерики для создания прокси-объекта
В классе Solution создайте публичный метод getProxy |
— Привет! И еще одна радостная тема – RMI. RMI расшифровывается Remote Method Invokation – удаленный вызов методов. Или другими словами RMI – это механизм, который позволяет объекту в одной Java-машине вызывать методы объекта в другой Java-машине, даже если они находятся на разных компьютерах, в разных странах, на разных сторонах земного шара.
— Ничего себе! Звучит очень круто.
— Ага. Но я постараюсь дать только обзорную лекцию. Тут, если глубоко копать, можно запутаться в нюансах работы.
Но если не ударяться в крайности, то RMI не только очень прост, но и значительно упрощает жизнь программиста. За что ему глубокий респект.
Итак, мы хотим, чтобы один объект, находящийся в одной Java-программе, смог вызвать метод у объекта, находящегося в другой Java-программе. Где-бы эти программы запущены не были.
Мы рассмотрим самый простой пример, когда обе программы запущены на одном компьютере. Чтобы программы могли взаимодействовать через интернет, необходимы настройки в правах Java-машины, но сегодня мы это рассматривать не будем.
В Java удаленно можно вызывать только методы интерфейсов, но не классов.
Итак, у нас есть две программы, как же им вызывать методы друг друга?
Давай рассмотрим ситуацию, когда одна программа содержит в себе некоторый объект, а вторая хочет вызвать его методы. Назовем первую программу – сервером, а вторую – клиентом.
Я сначала дам пример кода, а потом мы его разберем.
— А что будет делать наша программа?
— Гм. Ну, давай для простоты, у программы будет один метод, который разворачивает переданную ему строку задом наперед.
— Вроде ничего.
— Гуд, тогда начнем:
Сначала нам понадобится интерфейс, который будет удовлетворять нашим требованиями:
Интерфейс для межпрограммного взаимодействия |
---|
interface Reverse extends Remote { public String reverse(String str) throws RemoteException; } |
Я создал интерфейс Reverse и добавил ему интерфейс-маркер Remote, а также исключение RemoteException. В процессе вызова метода могут происходить незапланированные сбои – тогда будет кидаться это исключение.
Затем нам нужно написать серверный класс, который бы реализовывал этот интерфейс:
Класс для сервера |
---|
class ReverseImpl implements Reverse { public String reverse(String str) throws RemoteException { return new StringBuffer(str).reverse().toString(); } } |
— Вижу. В этом методе, мы разворачиваем строку задом наперед.
— Ага.
А теперь надо сделать этот объект доступным для вызова с другой программы. Вот как это делается:
Шаринг объекта | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public static final String UNIC_BINDING_NAME = «server.reverse»;
public static void main(String[] args) throws Exception //создание реестра расшареных объетов //усыпляем главный поток, иначе программа завершится |
Рассказываю по строкам.
Строка 2 – в переменной UNIC_BINDING_NAME храним придуманное нами уникальное имя нашего удаленного объекта (объекта, который доступен удаленно). Если программа шарит несколько объектов, у каждого должно быть свое уникальное имя. Уникальное имя нашего объекта — «server.reverse».
Строка 7 – собственно, создаем объект ReverseImpl, который будет доступен удаленно, и чьи методы будут вызваться.
Строка 10 — создаем специальный объект – реестр. В нем надо регистрировать объекты, которые мы шарим. Дальше ими занимается Java-машина. 2099 – это порт (уникальный номер, по которому другая программа может обратиться к нашему реестру объектов).
Т.е. чтобы обратиться к объекту, надо знать уникальный номер реестра объектов (порт), знать уникальное имя объекта и иметь такой же интерфейс, как и тот, который реализовывает удаленный объект.
— Ясно. Что-то вроде – позвонить по телефону (нужен номер) и попросить Соню (имя объекта)?
— Да. Теперь дальше.
Строка 12 – создание «заглушки». Заглушка – это специальный объект, который принимает информацию об удаленном вызове, распаковывает ее, десериализует переданные параметры методов и вызывает нужный метод. Затем сериализует результат или исключение, если оно было, и отсылает все это назад вызывающему.
— Ясно. Почти. Ты сказал, что «десериализует параметры метода». Значит, типами аргументов удаленного метода могут быть только сериализуемые?
— Ага. А как же иначе ты будешь пересылать их по сети? Есть, правда, и исключения – так называемые объекты, которые передаются по ссылке, но сегодня мы о них говорить не будем.
Скажем так, пересылать несериализуемые объекты нельзя, но если очень хочется, то можно. Но это хлопотное дело, знаешь ли.
— Ок.
— Тогда дальше.
Строка 14 – регистрируем в реестре заглушку нашего объекта под уникальным именем.
Строка 17 – усыпляем главный поток. Все удалённые вызовы обрабатываются в отдельных нитях. Главное, чтобы программа в это время работала. Так что тут просто отправляем главную нить спать, и всё.
— Ок.
— Отлично, тогда пример клиента:
Работа с удаленным объектом | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public static final String UNIC_BINDING_NAME = «server.reverse»;
public static void main(String[] args) throws Exception //получаем объект (на самом деле это proxy-объект) //Вызываем удаленный метод |
Объясняю код по строкам:
Строка 2 – уникальное имя удаленного объекта. Должно быть одинаковым на клиенте и сервере.
Строка 7 – создание объекта «Реестр удаленных объектов». Его порт 2099 должен быть таким же, как и у реестра у серверного приложения.
Строка 10 – получаем объект у реестра. Полученный объект является proxy-объектом и приводится к типу интерфейса. Интерфейс должен быть унаследован от интерфейса-маркера Remote.
Строка 13 – вызываем методы интерфейса так, как будто объект был создан в этой же программе. Никакой разницы.
— Круто! Это ж теперь можно писать распределенные приложения. Или игры типа морского боя для Android.
— Побойся бога, Амиго, операционная система Android была запрещена в 27 веке после третьей попытки захватить мир. У роботов к ней вообще доступа нет. Вас же потом от нее не оттянешь. Будете бегать и кричать «Убить всех человеков!».
— Гм. Ладно. Хотя надо будет у Диего еще спросить. Мало ли, может он что-нибудь интересное про нее расскажет.
— Вот и спроси. Ладно, давай до завтра.
— Пока, Риша, спасибо за интересную лекцию.
— Привет, Амиго! У меня есть задача, но я тебе её не дам.
Ладно, я шучу. Иди, решай свою задачу в Intellij IDEA.
Задачи |
---|
1. К серверу по RMI
Реализуйте логику метода run в CLIENT_THREAD. В нем будет имитироваться клиентская часть, которая коннектится к серверу. |
— Привет, Амиго!
Продолжаем наши уроки – учимся гуглить.
Вот тебе несколько заданий:
Что надо найти в Google | |
---|---|
1 | Как записать информацию в файл в произвольном месте |
2 | Как прочитать 10000-ю строку из файла, не читая предыдущих |
3 | Как преобразовать строку в Reader |
4 | Как преобразовать Writer в строку |
5 | Как создать прокси-объект |
6 | Как переопределить InvokeHandler |
7 | Как написать RMI клиент |
8 | Как написать RMI сервер |
9 | Как разрешитьRMI-доступ из других компьютеров сети |
10 | Распространённые RMI ошибки |
— Привет, Амиго!
Вот тебе дополнительный материал по теме.
Ссылка на дополнительный материал
— Привет, Амиго!
— Привет, Хулио.
— Как проходит обучение? Кстати, я уже скачиваю новый сезон «Доктора Кто». После того, как Китай стал самым сильным государством, все сериалы теперь на китайском.
— Обучение проходит отлично, с трудом решаю задачи. Тогда я за попкорном.
Оригинал видео на YouTube
— Привет, Амиго!
Вопросы к собеседованиям | |
---|---|
1 | Зачем нужен RandomAccessFile? |
2 | Что будет если файл, откуда читает RandomAccessFile, не существует? |
3 | Что будет если файл, куда пишет RandomAccessFile, не существует? |
4 | Зачем нужен класс StringReader? |
5 | Зачем нужен класс StringWriter? |
6 | Зачем нужен класс ByteArrayStream? |
7 | Зачем нужен класс PrintStream? Назовите места, где он используется? |
8 | Зачем нужен DynamicProxy? |
9 | Как работает RMI? |
10 | Объекты каких типов можно передавать по RMI? |
— Привет, боец!
— Поздравляю тебя с повышением уровня квалификации. Нам нужны отчаянные парни.
— Уверен, у тебя есть еще много нерешенных задач. Самое время решить парочку из них!
Привет всем.
Хочу запустить тестовый сервер RMI, при запуске возникает эксепшин:
Кликните здесь для просмотра всего текста
ComputeEngine exception: Server RemoteException; nested exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested excep
tion is:
java.lang.ClassNotFoundException: server.ComputeEngine_Stub
java.rmi.ServerException: Server RemoteException; nested exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested excep
tion is:
java.lang.ClassNotFoundException: server.ComputeEngine_Stub
at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(Stream
RemoteCall.java:247)
at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:
223)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:350)
at sun.rmi.registry.RegistryImpl_Stub.rebind(Unknown Source)
at java.rmi.Naming.rebind(Naming.java:160)
at server.ComputeEngine.main(ComputeEngine.java:40)
Caused by: java.rmi.UnmarshalException: error unmarshalling arguments; nested ex
ception is:
java.lang.ClassNotFoundException: server.ComputeEngine_Stub
Caused by: java.lang.ClassNotFoundException: server.ComputeEngine_Stub
Я перечитал об этом кучу статей и форумов (кстати, оч частый вопрос).
Но побороь эту проблему не смог. Кроме того, года 3 назад я использовал это rmi в своем проекте и все работало, а тут — никак.
Подробнее :
— проект лежит в папке D: estproj
— в проекте один пекедж — server.
— в пекедже два файла
Интерфейс:
Java | ||
|
и имплементация серера
Java | ||
|
Мои действия:
1) захожу в директорю D: estproj
2) запускаю там bat файл :
Windows Batch file | ||
|
Все ок. Классы скомпилены, стаб сгенерен. жар лежит в D: estproj
3) в D: estproj такде лежит java.policy файл
grant {
permission java.net.SocketPermission ‘127.0.0.1:8080’, ‘connect’;
permission java.net.SocketPermission ‘127.0.0.1:1024-65535′,’accept,listen,connect,resolve’;
permission java.io.FilePermission ‘D:\testproj\-‘, ‘read’;
};
4) открываю FAR, делаю set classpath= (после этого переменная classpath не определена)
4.1) start rmiregistry
5) открываю новое окно far в папке D: estproj
делаю set classpath=D: estproj<B
Posted by Imed Bouchrika
on October 22, 2013
in Java RMI
1 Comment
About Author
by Imed Bouchrika
Comments
-
John
Reply
Leave a Reply
Your email address will not be published. Required fields are marked *
Name *
Email *
Website
Related Posts
-
Java RMI Example : Auction System for Bidding on products concurrently
-
New Easy Tutorial for Java RMI using Eclipse
-
How to transfer or copy a file between computers using java RMI
-
Project Ideas for Java RMI : Distributed Applications
-
Group Chat Example using Java RMI with a Graphical User Interface
-
Java RMI Example : Group Chat Implementation
In this example we are going to talk about java.rmi.RemoteException
. This the most general checked exception that may occur during the lookup or the execution of a Remote Procedure Call (RPC). As might you know, Java provides a very convenient API that enables you to create remote servers that can host Remote Procedure Cal services, as well as clients that can easily consumed these services. All of these infrastracture is provided by the Java RMI package.
If you are familiar with Web Services or the SOAP protocol, you should be already acquainted with the notion of Remote Method calls, as the follow the same principles. One could say that a Web Service is basically RPC over the Web.
1. A simple RMI Application
In this example were are not going to dive deep into the RMI package, but we are going to create a simple RMI application in order to demonstrate java.rmi.RemoteException
. This application will have a simple RMI server that provides an implementation of a remote method, as well as a client that can remotely call that method. Let’s go through the code.
First of all you need to create a remote interface. This is the interface that make the communication between the client and the server possible. The servers exposes concrete implementations of the methods in that interface. On the other side, the clients uses that interface to invoke the remote procedures provided by the server.
RemoteInterface .java:
package com.javacodegeeks.core.rmi.rminterface; import java.rmi.Remote; import java.rmi.RemoteException; public interface RemoteInterface extends Remote{ public String capitalize(String str) throws RemoteException; }
Here is also a configuration class (mostly for convenience) :
Configuration.java:
package com.javacodegeeks.core.rmi.rminterface; public class Configuration { public static final int REMOTE_PORT = 8888; public static final String REMOTE_ID = "RMI_EXAMPLE"; public static final String REMOTE_HOST = "localhost"; }
OK now let’s go to the server side. Remember that the server has to provide a concrete implementation of RemoteInterface
:
RMIImplementation.java:
package com.javacodegeeks.core.rmi.remoteserver; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import com.javacodegeeks.core.rmi.rminterface.RemoteInterface; public class RMIImplementation extends UnicastRemoteObject implements RemoteInterface{ protected RMIImplementation() throws RemoteException { super(); } private static final long serialVersionUID = 1L; @Override public String capitalize(String str) throws RemoteException { return str.toUpperCase(); } }
And now it has to expose this capitalize
implementation to the public:
RemoteServer.java :
package com.javacodegeeks.core.rmi.remoteserver; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import com.javacodegeeks.core.rmi.rminterface.Configuration; public class RemoteServer { public static void main(String[] args) throws RemoteException, AlreadyBoundException { RMIImplementation rmiImplementation = new RMIImplementation(); Registry registry = LocateRegistry.createRegistry(Configuration.REMOTE_PORT); registry.bind(Configuration.REMOTE_ID, rmiImplementation); } }
On on the client side, the client has to connect to the server an simply look up the method it wants.
RemoteClient.java:
package com.javacodegeeks.core.rmi.remoteclient; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import com.javacodegeeks.core.rmi.rminterface.Configuration; import com.javacodegeeks.core.rmi.rminterface.RemoteInterface; public class RemoteClient { public static void main(String[] args) throws RemoteException, NotBoundException { Registry reg = LocateRegistry.getRegistry(Configuration.REMOTE_HOST, Configuration.REMOTE_PORT); RemoteInterface rmiInterface= (RemoteInterface) reg.lookup(Configuration.REMOTE_ID); String str = "javacodegeeks rock!"; System.out.println("RMI returns:"+rmiInterface.capitalize(str)); } }
To get an overview of the structure of the project that I’ve created, take a look at the following picture:
If you run RemoteServer
and then RemoteClient
, RemoteClient
will produce this output:
RMI returns:JAVACODEGEEKS ROCK!
2. An example of java.rmi.RemoteException
Now, imagine that as your remote methods is executed, suddenly something goes wrong and it throws an exception. A java.rmi.RemoteException
will be throw to the client.
So let’s change :
@Override public String capitalize(String str) throws RemoteException { return str.toUpperCase(); }
to :
@Override public String capitalize(String str) throws RemoteException { throw new UnexpectedException("Very unexpected"); //return str.toUpperCase(); }
Now, if you run the server first and then the client, here is the output:
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: java.rmi.UnexpectedException: Very unexpected at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:353) at sun.rmi.transport.Transport$1.run(Transport.java:177) at sun.rmi.transport.Transport$1.run(Transport.java:174) at java.security.AccessController.doPrivileged(Native Method) at sun.rmi.transport.Transport.serviceCall(Transport.java:173) at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:556) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:811) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:670) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744) at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:275) at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:252) at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:161) at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194) at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148) at com.sun.proxy.$Proxy0.capitalize(Unknown Source) at com.javacodegeeks.core.rmi.remoteclient.RemoteClient.main(RemoteClient.java:21) Caused by: java.rmi.UnexpectedException: Very unexpected at com.javacodegeeks.core.rmi.remoteserver.RMIImplementation.capitalize(RMIImplementation.java:20) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322) at sun.rmi.transport.Transport$1.run(Transport.java:177) at sun.rmi.transport.Transport$1.run(Transport.java:174) at java.security.AccessController.doPrivileged(Native Method) at sun.rmi.transport.Transport.serviceCall(Transport.java:173) at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:556) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:811) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:670) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744)
As we’ve mentioned in the introduction, java.rmi.RemoteException
is the most general checked exception that may occur during the lookup or the execution of a Remote Procedure Call (RPC). It is important to note that it extends java.io.IOException
. But the most significant thing is that it is seldom found as a standalone exception in the wild. It mostly communicated along with its numerous sub classes. Some of them are : ConnectException
, ConnectIOException
, MarshalException
, NoSuchObjectException
, ServerError
, UnexpectedException
, UnmarshalException
. So as you can imagine each one of them is thrown for a variety of reasons. As always they are accompanied with a very explanatory message that will help you trace the root of the problem.
A good starting point when trying to debug this exception is to solve communication issues first. For example be sure that the client and server can communicate with each other. Then make sure that you load the correct remote ID in both sides. What you should mostly do then is try to debug the remote method implementation on the server side.
Note that RemoteException
also holds a reference to the most specific underlying exception that caused it. In the case of our example, that nested exception is a java.rmi.UnexpectedException
. This is quite helpful as you can trace the exception that caused the more general RemoteException
. That is important for debugging because, when an exception is thrown at the server, it usually reaches the client in the form of a RemoteException
. So it’s important to go deeper that that, and find out what truly cased the exception. So after solving communication and configuration issues, you can narrow down your work at debugging the remote method implementation, always following the nested exceptions.
Download the Source Code
This was an example on java.rmi.RemoteException
and how to solve RemoteException
. You can download the source code of this example here : RMIExample.zip
Nikos has graduated from the Department of Informatics and Telecommunications of The National and Kapodistrian University of Athens. During his studies he discovered his interests about software development and he has successfully completed numerous assignments in a variety of fields. Currently, his main interests are system’s security, parallel systems, artificial intelligence, operating systems, system programming, telecommunications, web applications, human – machine interaction and mobile development.