[Программирование, Java, Компиляторы] Передача и вызов лямбд на сервере и отказаться от docker/deploy/…
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
При разработке клиент-серверного приложения, у меня всегда появляется вопрос, а как я его буду разворачивать на сервере, упаковать его в jar/war/docker после написания кода, а потом еще надо передать на сервер, и еще много сделать телодвижений чтоб просто засунуть кусок кода на сервере.Было бы хорошо просто передать на сервер код, как лямбду, так же как мы передаем лямбду в функцию, так же на сервер ее передать.Но у меня появилась мысль как сделать этот процесс проще, и у меня что-то получилось.
1 | var query = TcpQuery
2 | .create(IEnv.class).host("myserver.com").port(9988)
3 | .build();
4 |
5 | query.apply(
6 | env -> env.processes().stream().filter(
7 | p -> p.getName().contains("java")
8 | )
9 | .collect(Collectors.toList())
10| ).forEach(System.out::println);
Вот код на java,
- Строки с 1 по 5 включительно и 10 - работают на клиенте
- А строки с 6 по 9 (тело лямбды env->...) работают на сервере
И это не псевдокод, это реальный рабочий код на Java (11).Действительно код строк с 6 по 9 (само тело лямбды) передаются на сервер и исполняются на сервере, при том сервер о этом куске кода ничего не знает, он принимает этот код исполняет у себя, и отдает обратно клиенту - а я дальше продолжаю писать код без всякого deploy, не выходя из IDE (Idea/Eclipse/etc...).Статья будет о том, что такое Serializable Lambda в Java, и как передавать байт-код таких лямбд на сервер, без перезапуска сервера, т.е. как можно в теории отказаться от привычных схем deploy приложений и писать более удобный api (это моя фантазия).Допустим у нас есть такой интерфейс IEnv:
public interface IEnv {
List<OsProc> processes();
}
Который возвращает список процессов
public class OsProc implements Serializable {
public Optional<Integer> getPpid(){ return ... }
public int getPid(){ return ... }
public void setPid(int pid){ ... }
public String getName(){ return ... }
public Optional<String> getCmdline(){ return ... }
}
И вот таким простым способом я передаю лямбду на сервер
var query = TcpQuery
.create(IEnv.class).host("myserver.com").port(9988)
.build();
query.apply(
env -> env.processes().stream().filter(
p -> p.getName().contains("java")
)
.collect(Collectors.toList())
).forEach(System.out::println);
Введение - как это работает ?
При разработке клиентского приложения на Java
- Мы создаем набор исходных файлов, допустим Client.java
- Компилятор генерирует байт-код - файл Client.class
- При вызове query.apply() - мы передаем ссылку на лямбду env -> env.proc...toList())
- Реализация query.apply():
- Принимает ссылку на лямбду
- Для лямбды обнаруживает имя класса (например Client) и метода (например lambda1) реализующего лямбду
- Отыскивает среди ресурсов программы соответствующий байт-код класса (Client.class) и его метода
- Загружает байт-код реализации лямбды и отправляет его на сервер
- Сервер принимает байт-код лямбды
- Генерирует в памяти класс в который вставляет принятый байт-код
- Загружает этот класс в память и через рефлексию получает доступ к лямбде
- Возвращает идентификатор этого метода обратно
- Принимает идентификатор метода и делает вызов его на сервере
- Сервер выполняет ранее скомпилированный класс/метод/лямбду
- Возвращает результат выполнения
- Принимает результат серверного вызова и возвращает его как результат локального вызова
- Возврат результата вызова query.apply()
Далее в статье будет рассмотрено я на примере очень простой библиотеки покажу, как запустить свои наработки в режиме клиент-сервера, и писать клиентов без выхода из IDE и перезапуска серверов.Как воспользоваться? Что бы с этим по играть, воспользуйтесь моим pet project-ом:v 1.0 https://github.com/gochaorg/trambda/releases/tag/1.0maven
<dependency>
<groupId>xyz.cofe</groupId>
<artifactId>trambda</artifactId>
<version>1.0</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>xyz.cofe</groupId>
<artifactId>trambda-core</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>xyz.cofe</groupId>
<artifactId>trambda-tcp</artifactId>
<version>1.0</version>
</dependency>
Чтобы воспользоваться, у вас должна быть какая нибудь библиотека к которой вы хотите обращаться по сети (TCP)В моем примере это будет очень простая библиотека, в ней всего три файла:
- OsProc.java - описывает процесс ОС (описание было выше)
- IEnv.java - Интерфейс получения списка процессов для ОС (описание было выше)
- LinuxEnv.java - Получение списка процессов для ОС Linux - реализация IEnv
Библиотека из примера работает так:
package xyz.cofe.trambda.demo.api;
import org.junit.jupiter.api.Test;
public class LinuxEnvTest {
@Test
public void test(){
var env = new LinuxEnv();
env.processes().stream()
.filter(p->p.getName().equalsIgnoreCase("java"))
.forEach(System.out::println);
}
}
LinuxEnv - это простой класс, он устроен так:
package xyz.cofe.trambda.demo.api;
import java.util.ArrayList;
import java.util.List;
import xyz.cofe.io.fs.File;
public class LinuxEnv implements IEnv {
@Override
public List<OsProc> processes(){
ArrayList<OsProc> procs = new ArrayList<>();
File procDir = new File("/proc");
procDir.dirList().stream()
.filter( d -> d.getName().matches("\\d+") && d.isDir() )
.map(OsProc::linuxProc)
.forEach(procs::add);
return procs;
}
}
Код его тривиален, он сканирует каталог /proc и находит описание процесса ОС, (для Linux все процессы сервера отображаются в виде файлов/подкаталогов /proc)Скомпилируем библиотеку (или возьмем свою…)Клонируем репозиторий (git commit 67ec260)
> git clone https://github.com/gochaorg/trambda.git
Клонирование в «trambda»...
remote: Enumerating objects: 978, done.
remote: Counting objects: 100% (978/978), done.
remote: Compressing objects: 100% (464/464), done.
remote: Total 978 (delta 308), reused 862 (delta 195), pack-reused 0
Получение объектов: 100% (978/978), 715.70 KiB | 559.00 KiB/s, готово.
Определение изменений: 100% (308/308), готово.
Собираем библиотеку для демонстрации
user@user-Modern-14-A10RB:22:10:35:~/Загрузки/sample-tr:
> cd trambda/trambda-demo/tr-demo-api/
user@user-Modern-14-A10RB:22:10:49:~/Загрузки/sample-tr/trambda/trambda-demo/tr-demo-api:
> mvn clean package install
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 13.933 s
[INFO] Finished at: 2021-04-18T22:11:11+05:00
[INFO] ------------------------------------------------------------------------
В каталоге target должен оказаться файл jar с нашей библиотекой
user@user-Modern-14-A10RB:22:13:13:~/Загрузки/sample-tr/trambda/trambda-demo/tr-demo-api:
> ll target/
итого 48
drwxrwxr-x 10 user user 4096 апр 18 22:11 ./
drwxrwxr-x 4 user user 4096 апр 18 22:11 ../
drwxrwxr-x 3 user user 4096 апр 18 22:11 classes/
drwxrwxr-x 3 user user 4096 апр 18 22:11 generated-sources/
drwxrwxr-x 3 user user 4096 апр 18 22:11 generated-test-sources/
drwxrwxr-x 2 user user 4096 апр 18 22:11 maven-archiver/
drwxrwxr-x 3 user user 4096 апр 18 22:11 maven-status/
drwxrwxr-x 4 user user 4096 апр 18 22:11 site/
drwxrwxr-x 2 user user 4096 апр 18 22:11 surefire-reports/
drwxrwxr-x 3 user user 4096 апр 18 22:11 test-classes/
-rw-rw-r-- 1 user user 6337 апр 18 22:11 tr-demo-api-1.0-SNAPSHOT.jar
Запуск сервераТеперь когда библиотека собрана копируем сервер с github и распаковываем архив
user@user-Modern-14-A10RB:22:37:25:~/Загрузки/sample-tr:
> wget https://github.com/gochaorg/trambda/releases/download/1.0/trambda-tcp-serv-cli.zip
--2021-04-18 22:37:31-- https://github.com/gochaorg/trambda/releases/download/1.0/trambda-tcp-serv-cli.zip
Распознаётся github.com (github.com)... 140.82.121.4
Подключение к github.com (github.com)|140.82.121.4|:443... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа... 302 Found
Адрес: https://github-releases.githubusercontent.com/350075998/47380d00-9b40-11eb-90a4-4e353f42e67c?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20210418%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210418T173731Z&X-Amz-Expires=300&X-Amz-Signature=97ade1f58bfbe1eaa320805179987e8c4df730b9f5eddf24c05662fb676caafe&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=350075998&response-content-disposition=attachment%3B%20filename%3Dtrambda-tcp-serv-cli.zip&response-content-type=application%2Foctet-stream [переход]
--2021-04-18 22:37:31-- https://github-releases.githubusercontent.com/350075998/47380d00-9b40-11eb-90a4-4e353f42e67c?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20210418%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210418T173731Z&X-Amz-Expires=300&X-Amz-Signature=97ade1f58bfbe1eaa320805179987e8c4df730b9f5eddf24c05662fb676caafe&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=350075998&response-content-disposition=attachment%3B%20filename%3Dtrambda-tcp-serv-cli.zip&response-content-type=application%2Foctet-stream
Распознаётся github-releases.githubusercontent.com (github-releases.githubusercontent.com)... 185.199.111.154, 185.199.108.154, 185.199.109.154, ...
Подключение к github-releases.githubusercontent.com (github-releases.githubusercontent.com)|185.199.111.154|:443... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа... 200 OK
Длина: 12107487 (12M) [application/octet-stream]
Сохранение в: «trambda-tcp-serv-cli.zip»
trambda-tcp-serv-cli.zip 100%[========================================================================================================================================>] 11,55M 3,75MB/s за 3,1s
2021-04-18 22:37:35 (3,75 MB/s) - «trambda-tcp-serv-cli.zip» сохранён [12107487/12107487]
user@user-Modern-14-A10RB:22:37:35:~/Загрузки/sample-tr:
> ll
итого 11836
drwxrwxr-x 3 user user 4096 апр 18 22:37 ./
drwxr-xr-x 11 user user 4096 апр 18 22:00 ../
drwxrwxr-x 10 user user 4096 апр 18 22:10 trambda/
-rw-rw-r-- 1 user user 12107487 апр 12 03:36 trambda-tcp-serv-cli.zip
user@user-Modern-14-A10RB:22:37:42:~/Загрузки/sample-tr:
> unzip trambda-tcp-serv-cli.zip
Archive: trambda-tcp-serv-cli.zip
creating: trambda-tcp-serv-cli/
creating: trambda-tcp-serv-cli/jars/
inflating: trambda-tcp-serv-cli/jars/asm-9.1.jar
inflating: trambda-tcp-serv-cli/jars/jline-2.14.6.jar
inflating: trambda-tcp-serv-cli/jars/iofun-1.0.jar
inflating: trambda-tcp-serv-cli/jars/groovy-swing-3.0.7.jar
inflating: trambda-tcp-serv-cli/jars/groovy-console-3.0.7.jar
inflating: trambda-tcp-serv-cli/jars/groovy-xml-3.0.7.jar
inflating: trambda-tcp-serv-cli/jars/trambda-tcp-serv-cli-1.0.jar
inflating: trambda-tcp-serv-cli/jars/ecolls-1.10.jar
inflating: trambda-tcp-serv-cli/jars/trambda-core-1.0.jar
inflating: trambda-tcp-serv-cli/jars/slf4j-api-1.7.25.jar
inflating: trambda-tcp-serv-cli/jars/asm-tree-9.1.jar
inflating: trambda-tcp-serv-cli/jars/asm-util-9.1.jar
inflating: trambda-tcp-serv-cli/jars/fs-1.2.jar
inflating: trambda-tcp-serv-cli/jars/logback-classic-1.2.3.jar
inflating: trambda-tcp-serv-cli/jars/trambda-tcp-1.0.jar
inflating: trambda-tcp-serv-cli/jars/groovy-groovysh-3.0.7.jar
inflating: trambda-tcp-serv-cli/jars/groovy-templates-3.0.7.jar
inflating: trambda-tcp-serv-cli/jars/asm-analysis-9.1.jar
inflating: trambda-tcp-serv-cli/jars/text-1.0.jar
inflating: trambda-tcp-serv-cli/jars/logback-core-1.2.3.jar
inflating: trambda-tcp-serv-cli/jars/groovy-3.0.7.jar
inflating: trambda-tcp-serv-cli/jars/cbuffer-1.3.jar
creating: trambda-tcp-serv-cli/bin/
inflating: trambda-tcp-serv-cli/bin/trambda-tcp-serv.bat
inflating: trambda-tcp-serv-cli/bin/trambda-tcp-serv
user@user-Modern-14-A10RB:22:37:50:~/Загрузки/sample-tr:
> rm trambda-tcp-serv-cli.zip
После копируем нашу библиотеку в каталог trambda-tcp-serv-cli/jars
ser@user-Modern-14-A10RB:22:40:47:~/Загрузки/sample-tr:
> cp -v trambda/trambda-demo/tr-demo-api/target/tr-demo-api-1.0-SNAPSHOT.jar trambda-tcp-serv-cli/jars/
'trambda/trambda-demo/tr-demo-api/target/tr-demo-api-1.0-SNAPSHOT.jar' -> 'trambda-tcp-serv-cli/jars/tr-demo-api-1.0-SNAPSHOT.jar'
Нам понадобиться подготовить скрипт запуска, скрипт на языке groovy (не беспокойтесь, это не обязательно именно так запускать, все можно сделать на голой Java)Возьмем скрипт из примера:
> cat trambda/trambda-tcp-serv-cli/src/test/samples/sample-1.groov
// Сервис xyz.cofe.trambda.demo.api.LinuxEnv
// будет запущен на порту 9988, и будет доступен с любого IP
app.service( "0.0.0.0:9988", new xyz.cofe.trambda.demo.api.LinuxEnv() ) {
daemon false
// Указываем настройки безопасности
security {
// Какие API/Методы будут доступны извне
allow {
// method("System") {
// methodOwner ==~ /java.lang.System/ && methodName in ['gc']
// }
// field( "System.out" ) {
// fieldOwner ==~ /java.lang.System/ && fieldName in ['out','in','err'] && readAccess
// }
invoke( 'Java compiler' ){
methodOwner ==~ /java\.lang\.invoke\.(LambdaMetafactory|StringConcatFactory)/
}
invoke( 'Java collections' ){
methodOwner ==~ /java\.util\.(stream\.(Stream|Collectors)|(List))/
}
invoke( 'Java lang' ){
methodOwner ==~ /java\.lang\.String/
}
invoke( 'Api '){
methodOwner ==~ /xyz\.cofe\.trambda\.demo\.api\.(IEnv|OsProc)/
}
}
// Для всех остальных случаев - запрещаем вызов
deny {
any("ban all")
}
}
}
Запускаем сервер
user@user-Modern-14-A10RB:22:56:08:~/Загрузки/sample-tr:
> bash ./trambda-tcp-serv-cli/bin/trambda-tcp-serv -s trambda/trambda-tcp-serv-cli/src/test/samples/sample-1.groovy
# [main] INFO x.c.t.tcp.serv.cli.TcpServerCLI - starting xyz.cofe.trambda.tcp.serv.cli.TcpServerCLI
# [main] INFO x.c.t.tcp.serv.cli.TcpServerCLI - executeScript( "trambda/trambda-tcp-serv-cli/src/test/samples/sample-1.groovy", UTF-8 )
# [main] INFO x.c.t.tcp.serv.cli.TcpServerCLI - registry class xyz.cofe.trambda.demo.api.LinuxEnv on 0.0.0.0:9988
# [main] INFO x.c.t.tcp.serv.cli.TcpServerCLI - starting service xyz.cofe.trambda.demo.api.LinuxEnv@55e7a35c on /0.0.0.0:9988
# [main] DEBUG x.c.t.tcp.serv.cli.TcpServerCLI - create server socket
# [main] DEBUG x.c.t.tcp.serv.cli.TcpServerCLI - bind server socket /0.0.0.0:9988
# [main] DEBUG x.c.t.tcp.serv.cli.TcpServerCLI - server started
Все, сервер запущен, теперь можем им пользоватьсяЗапуск клиентаКлиента можно взять из примера (ClientTest.java)
package xyz.cofe.trambda.demo.client;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import xyz.cofe.trambda.demo.api.IEnv;
import xyz.cofe.trambda.tcp.TcpQuery;
public class ClientTest {
@Test
public void test01(){
var query = TcpQuery
.create(IEnv.class).host("localhost").port(9988)
.build();
query.apply(
env -> env.processes().stream().filter(p ->
p.getName().contains("java"))
.collect(Collectors.toList())
).forEach(System.out::println);
}
}
Запустим и посмотрим, что он выведет, часть логов я опущу - т.к. для пояснения работы они не важны
Что происходит на сервере ?часть логов я опущу - т.к. для пояснения работы они не важны
Настройка безопасности сервераСовсем не обязательно использовать сервер через скрипты запуска, сервер можно встроить как часть своего приложения и настраивать так:
// Создание TCP сокета
ServerSocket ssocket = new ServerSocket(port);
// Настраиваем сокет
ssocket.setSoTimeout(1000*5);
// Создаем сервер
server = new TcpServer<IEnv>(
// Передаем сокет
ssocket,
// Передаем функцию получения сервиса для новой сессии
s -> new LinuxEnv(),
// Настраиваем безопасность
SecurityFilters.create(s -> {
// Разрешаем вызовы строго - определенных методов
s.allow( a -> {
// Публикуемый API нашего сервиса
a.invoke("demo api", c->
c.getOwner().matches(
"xyz\\.cofe\\.trambda\\.tcp\\.demo\\.([\\w\\d]+)"));
// Работа с коллекциями
a.invoke("java collections api", c->c.getOwner().matches(
"java\\.util\\.(List)|java\\.util\\.stream\\.([\\w\\d]+)"));
// Работа с Java строками
a.invoke("java lang api", c->
c.getOwner().matches("java\\.lang\\.(String)"));
// Методы которые использует компилятор Java
a.invoke("java compiler", c->
c.getOwner().matches(
"java\\.lang\\.invoke\\.(LambdaMetafactory|StringConcatFactory)"));
});
// Все остальное запрещаем
s.deny().any("by default");
})
);
// Указываем что Thread сервера будет запущен как фоновый
server.setDaemon(true);
// Запускаем сервер
server.start();// Создание TCP сокета
ServerSocket ssocket = new ServerSocket(port);
// Настраиваем сокет
ssocket.setSoTimeout(1000*5);
Код приведенный выше вроде кажется понятным, но наверно следует дать несколько пояснений
- Правила проверки безопасности указываются в виде списка фильтров
- Список фильтров обрабатывается последовательно и соответственно первые правила имеют более высокий приоритет, нежели последние
- В списке могут быть как разрешающие, так и запрещающие правила
- Java компилятор генерирует
- вызовы LambdaMetafactory когда вы используете лямбды в своем коде
- вызовы StringConcatFactory когда вы применяете оператор + к строкам
- Вы также можете контролировать обращения к полям класса на чтения/запись - см. SecurityFilters.java / PredicateBuilder#field
- Для доступа к стандартным функция, там даже как Object.toString() то, же надо будет задать правила доступа
Более подробную информацию о работе можно найти на странице проекта, например на github или git pagesЧем это может быть полезно?Резонный вопрос, чем может быть полезно и чем оно отличаеться от уже существующих, например RMI, gRPC ? Давайте рассмотрим отличияПротоколов передачи данных и вызова процедур много, будут рассмотрены некоторые из известных автору. Эти протоколы можно разделить на несколько категорий/свойств:
- Передача данных
- Строго ограниченные форматы данных / типы данных
- Гибкие форматы данных / комбинированные/структурированные типы данных
- Передача программного кода
- Ограничения на алгоритмы
- Простые выражения
- Циклы/ветвления
- Процедуры/функции/объекты…
- Ограничения на интерпретацию алгоритмов
- Слабая типизация
- Строгая типизация
- Безопасность
- авторизация и т.д.
- время исполнения
- … и т.д.
- Поддержка существующий решений
- Потребность в перезапуске серверов, что бы опробовать новые решения
- Возможность на ходу опробовать новые решения
- Профилирование выполнения
ФичаJava-RMISOAPREST-JSONSQLGraphQLHadoopПередача данных++++++Строго ограниченные форматы данных+/-+/---+-Гибкие форматы данных+/-+/-++-+Передача программного кода---+-+Простые выражения---+-+Циклы/ветвления---+-+Процедуры/функции---+-+программные объекты…---?-?Слабая типизация--+--+Строгая типизация++-++-авторизация++++++время исполнения???+/-??Потребность в перезапуске серверов++?-+-Возможность находу опробывать новые решения--?+-+Профилирование выполнения++?++?
- Большинство протоколов ориентированы только на передачу данных (Java-RMI, SOAP, REST-JSON, GraphQL)
- Часть из них работают со строго типизированными данными (Java-RMI, SOAP, GraphQL)
- Другие (REST-JSON, Hadoop) со слабо типизированными
- Небольшое кол-во протоколов поддерживают еще передачу программного кода (SQL, Hadoop)
Наличие строгой типизации и передачу программного кода из рассмотренных есть только в SQLВ предлагаемом проекте есть следующие возможности, с оговорками
- Передача данных (*)
- Гибкие форматы данных / комбинированные / структурированные типы данных
- Передача программного кода
- Простые выражения
- Циклы/ветвления
- Процедуры/функции/
- программные объекты… (**)
- Ограничения на интерпретацию алгоритмов
- Строгая типизация
- Безопасность (***)
- Поддержка существующий решений
- Возможность на ходу опробовать новые решения
- Профилирование выполнения (****)
Оговорки
- (*)
- передаваемые типы должны быть Serializable
- требуется апробация Proxy для интерфейсов
- (**)
- требуется апробация Proxy для объектов - очень неоднозначный вопрос
- (***)
- реализована проверка байт-кода, без учета текущего пользователя
- не реализован механизм аутентификации, см план реализации
- (****)
- еще не реализовано, см план реализации
Область примененияПоскольку проект только начат, говорить о реальном применении рано, можно говорить о потенциальном примененииВозможны следующий области применения
- фильтрация данных в программах написанных на Java по аналогии SQL WHERE (уже есть)
- выполнение серверных процедур по аналогии RPC/RMI/SOAP/… (уже есть)
- подписка клиента на события сервера
- масштабирование нагрузки (как частный случай реализации сетевого протокола)
При дальнейшем развитии возможно автоматическая прозрачная трансляция JAVA/Kotlin/Scala кода в целевые системы (SQL, MongoDB, REST, …)Данное возможно при условии развития функции декомпиляции байт-кода в код AST/Java/…, по факту такая функция реализована в декомпиляторе JADЧто собственно ведет к уменьшению издержек при разработке ПО.Как же оно внутри работает ?Идея была проста, весь код Java который компилируется, обычно сохраняется в виде байт-кода который является файлом с расширением .class, а для любого объекта java можно узнать класс, обычно это выглядит так: obj.getClass()Через объект Class можно узнать его название, и соответственно через Class.getResource(‘имя класса’) можно получить URL ссылку на данный файл.“Гладко было на бумаге, но забыли про овраги”Такова была идея, но… есть нюансыПроцесс разработки, как найти байт код?Допустим у нас есть такой код
package xyz.cofe.trambda.l1;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
public class SimpleLambdaTest {
@Test
public void javaLambda01(){
Function<Function<String,String>,String> test = (f) -> {
System.out.println("f="+f.getClass());
return null;
};
test.apply( x -> x.repeat(4) );
}
}
При выполнении теста будет вот это:f=class xyz.cofe.trambda.l1.SimpleLambdaTest$$Lambda$235/0x0000000800142040По идее у нас в каталоге test-classes/ должен быть файл SimpleLambdaTest$$Lambda$235, но такого файла не видно
user@user-Modern-14-A10RB:00:41:32:~/code/trambda/trambda-core/target/test-classes/xyz/cofe/trambda/l1:
> ll
итого 12
drwxrwxr-x 2 user user 4096 апр 25 00:40 ./
drwxrwxr-x 5 user user 4096 апр 25 00:40 ../
-rw-rw-r-- 1 user user 2162 апр 25 00:40 SimpleLambdaTest.class
Тогда посмотрим байт код
> javap -p SimpleLambdaTest.class
Compiled from "SimpleLambdaTest.java"
public class xyz.cofe.trambda.l1.SimpleLambdaTest {
public xyz.cofe.trambda.l1.SimpleLambdaTest();
public void javaLambda01();
private static java.lang.String lambda$javaLambda01$1(java.lang.String);
private static java.lang.String lambda$javaLambda01$0(java.util.function.Function);
}
По факту где-то lambda$javaLambda01$1 или lambda$javaLambda01$0 находиться код нашей лямбды, но можно долго гадать, но это не наш подходПо факту сопоставить class и лямбду в Java с разбегу не удастся, а гадать это сразу путь на костыли.В Java есть интересный интерфейс Serializable, и этим интерфейсом можно пометить лямбдуНапример так
package xyz.cofe.trambda.l2;
import java.io.Serializable;
import java.util.function.Function;
public interface Fn<A,Z> extends Function<A,Z> , Serializable {
}
или вот так
Runnable r = (Runnable & Serializable)() -> System.out.println("Serializable!");
Фактически это заставит компилятор Java сгенерировать дополнительный код, давайте возьмем код теста выше и немного его модифицируем
package xyz.cofe.trambda.l2;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
public class SerialLambdaTest {
@Test
public void serLambda01(){
Fn<Fn<String,String>,String> test = (lambda) -> {
System.out.println("lambda="+lambda.getClass());
Method writeReplace = null;
try{
writeReplace = lambda.getClass().getDeclaredMethod("writeReplace");
writeReplace.setAccessible(true);
SerializedLambda sl = (SerializedLambda) writeReplace.invoke(lambda);
System.out.println(sl);
} catch( NoSuchMethodException | InvocationTargetException | IllegalAccessException e ) {
e.printStackTrace();
}
return null;
};
test.apply( x -> x.repeat(4) );
}
}
Теперь результат будет таким, его разбор будет ниже.
lambda=class xyz.cofe.trambda.l2.SerialLambdaTest$$Lambda$235/0x0000000800142040
SerializedLambda[capturingClass=class xyz.cofe.trambda.l2.SerialLambdaTest,
functionalInterfaceMethod=xyz/cofe/trambda/l2/Fn.apply:(Ljava/lang/Object;)
Ljava/lang/Object;,
implementation=invokeStatic
xyz/cofe/trambda/l2/SerialLambdaTest.lambda$serLambda01$3fed5817$1:
(Ljava/lang/String;)Ljava/lang/String;,
instantiatedMethodType=(Ljava/lang/String;)Ljava/lang/String;, numCaptured=0]
А при просмотре байт кода мы увидим
user@user-Modern-14-A10RB:00:51:48:~/code/trambda/trambda-core/target/test-classes/xyz/cofe/trambda/l2:
> javap -p SerialLambdaTest.class
Compiled from "SerialLambdaTest.java"
public class xyz.cofe.trambda.l2.SerialLambdaTest {
public xyz.cofe.trambda.l2.SerialLambdaTest();
public void serLambda01();
private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda);
private static java.lang.String lambda$serLambda01$3fed5817$1(java.lang.String);
private static java.lang.String lambda$serLambda01$47b6c34$1(xyz.cofe.trambda.l2.Fn);
}
Первое что мы можем заметить - у нас появился метод $deserializeLambda$ пока не важно что делает этот метод, есть факт что добавление интерфейса Serializable меняет поведение компилятора.java.lang.invoke.SerializedLambda - это final класс, который содержит ряд интересных свойств, а именно
- String getImplClass() - Имя класса, содержащего метод реализации.
- String getImplMethodName() - название метода реализации.
В stdout можно заметить фрагментimplementation=invokeStatic xyz/cofe/trambda/l2/SerialLambdaTest.lambda$serLambda01$3fed5817$1:(Ljava/lang/String;)Ljava/lang/String;И в байт коде:private static java.lang.String lambda$serLambda01$3fed5817$1(java.lang.String);Т.е. можно однозначно установить байт-код для лямбдыСериализация байт кодаТеперь, на данном этапе прочесть байт код лямбды - дело техники, а именно так:
SerializedLambda sl = (SerializedLambda)writeReplace.invoke(lambda);
var implClassName = sl.getImplClass()
var implClassUrl =
labmda.getClass().getResource("/"+implClassName.replace(".","/")+".class");
implClassUrl - указывает на файл класса содержащего байт кодполучаем байт код из URL как массив байтов
byte[] classByteCode = null;
try{
classByteCode = IOFun.readBytes(implClassUrl);
} catch( IOException e ) {
throw new IOError(e);
}
После этого передаем этот набор байтов в библиотеку ASM для прочтения байт кода
var classReader = new ClassReader(classByteCode);
и читаем байт код нужного метода
ClassVisitor cv = new ClassVisitor(Opcodes.ASM9) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if( methName.equals(name) && descriptor!=null && descriptor.equals(methSign) ){
mdef0.set(new MethodDef(access,name,descriptor,signature,exceptions));
return dump(byteCodes::add);
}
return null;
}
};
cr.accept(cv, 0);
MethodVisior - это класс который получает байт код конкретного метода. Методы этого класса можно переопределить, рассмотрим часть реализации MethodDump extends MethodVisitor:
package xyz.cofe.trambda;
public class MethodDump extends MethodVisitor implements Opcodes {
...
@Override
public void visitParameter(String name, int access){
emit(new MParameter(name,access));
}
@Override
public void visitInsn(int opcode){
emit(new MInsn(opcode));
}
...
}
Данный класс принимает байт код через вызовы методов visitXXXX(...) - их много методов (по этому показа только часть)При каждом вызове метода генерируется объект который описывает вызов, например для visitInsn( op ) генерируется new MInsn(op), а потом этот объект передается выше emit(..) где уже этот объект передается в сетьВосстановление классов из байт кодаПосле этого как код передан по сети в виде набора объектов, эти объекты обратно собирается в байт код (с дополнительными проверками безопасности)Для этого в моей библиотеке есть класс xyz.cofe.trambda.MethodRestore его работа заключается примерно в следующем
public synchronized byte[] generate(){
// генерируем имя целевого класса
binClassName = className.replace('.', '/');
// Создаем ClassWriter (часть ASM)
// в котором будем вызывать методы visitXXXX( op )
var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V11,
Opcodes.ACC_PUBLIC|Opcodes.ACC_SUPER,
binClassName,null,
"java/lang/Object", null
);
// генерация метода
var mv = cw.visitMethod(
acc, // флаги как static public
name, // имя метода
desc, // параметры метода
sign, // сигнатура если есть Generic параметры
excepts); // исключения которые может генерировать метод
// Потом в цикле для каждого переданного объекта
// генерируем соответствующий вызов visitXXXX()
// генерируем код в той же последовательности, что и был прочитан
for( var bc : byteCodes ){
if( bc instanceof MCode )build((MCode) bc);
else if( bc instanceof MEnd )build((MEnd) bc);
else if( bc instanceof MLabel )build((MLabel) bc);
else if( bc instanceof MLineNumber )build((MLineNumber) bc);
else if( bc instanceof MVarInsn )build((MVarInsn) bc);
...
}
// Получаем байт код
return cw.toByteArray();
}
// Вызов visitCode() - начало метода
protected void build(MCode code){ mv.visitCode(); }
// Вызов visitEnd() - конец метода
protected void build(MEnd end){ mv.visitEnd(); }
protected void build(MTypeInsn tinst){
mv.visitTypeInsn(tinst.getOpcode(), tinst.getOperand());
}
Теперь когда у нас есть на сервере байт код, остается его загрузить и вызвать соответствующий методДля этого создаем свой ClassLoader
var byteCode = new MethodRestore()
.className(clName)
.methodName("lambda1")
.methodDef(mdef)
.generate();
ClassLoader cl = new ClassLoader(ClassLoader.getSystemClassLoader()) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
if( name!=null && name.equals(clName) ){
return defineClass(name,byteCode,0,byteCode.length);
}
return super.findClass(name);
}
};
Загружаем класс
System.out.println("try read class "+clName);
Class c = null;
try{
c = Class.forName(clName,true,cl);
System.out.println("class found "+c);
} catch( ClassNotFoundException e ) {
e.printStackTrace();
return;
}
Ищем нужный нам метод класса
Method m = null;
System.out.println("methods");
for( var delMeth : c.getDeclaredMethods() ){
System.out.println(""+delMeth);
if( delMeth.getName().equals(methName) ){
m = delMeth;
}
}
и вызываем его с параметрами
try{
Object arg0 = "abc";
System.out.println("call with "+arg0);
Object res = m.invoke(null, arg0);
System.out.println("result "+res);
} catch( IllegalAccessException | InvocationTargetException e ) {
e.printStackTrace();
}
ОграниченияКонечно у данного решения есть ряд ограничений
- Не для любого языка подойдет
- например для Kotlin придется дописывать реализацию, т.к. Kotlin по другому компилирует лямбды, такая же ситуация с Scala
- для динамических языков (Groovy, JavaScript) вообще наверно не подойдет - но там наверно можно и по другому передавать лямбды, например передавать AST дерево лямбд - все зависит от их представления промежуточного кода, чем бы это не являлось.
- Гарантировать типо безопасность - особенно когда разные версии библиотек на сервере и на клиенте - задача непростая, но еще не означает что невозможно
- Потенциально на клиенте может Java более новая, с другим байт-кодом нежели на сервере, сервер может не знать о каких либо конструкциях новой Java
- Библиотека не весь байт код передает, это связано с тем, что не весь байт код имеет объектное представление - наверно это решаемый вопрос, вопрос доработки.
- Отдельно - это вопрос передачи по сети
- Вопрос безопасности - технически это решаемо, но требует допиливание напильником
- Протокол передачи, моя библиотека использует TCP, но этот сетевой уровень всегда можно сменить на более подходящий вам, но конечно потребует реализации его, по факту сериализация лямбды и ее восстановления никак, не ограничивают сетевой уровень.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Haskell, Функциональное программирование, Rust] Как мы выбираем языки программирования в Typeable
- [Программирование, C++, Работа с 3D-графикой, Разработка игр, CGI (графика)] Vulkan. Руководство разработчика. Непрограммируемые стадии конвейера (перевод)
- [Программирование, C#, Unity] Немного о графиках, сплайнах и генерации ландшафта
- [Разработка веб-сайтов, JavaScript, Совершенный код, HTML] Целительная сила JavaScript (перевод)
- [Программирование, Проектирование и рефакторинг] Чистая архитектура. Часть II — Парадигмы программирования
- [Программирование, Алгоритмы, Математика] Вебинар от Яндекс.Практикума «Конечные автоматы в реальной жизни»: теория, кодинг и Q&A за один вечер
- [Занимательные задачки, Программирование, Алгоритмы, Node.JS, VueJS] Создаем кэшируемую пагинацию, которая не боится неожиданного добавления данных в БД
- [PHP, Программирование, Тестирование веб-сервисов] Работа с частичными моками в PHPUnit 10
- [Программирование микроконтроллеров, Умный дом, Интернет вещей, DIY или Сделай сам, Электроника для начинающих] Рожденные в карантине: беспроводной датчик и все-все-все. Битва роботов в конце
- [Программирование, C++, Промышленное программирование] Современный C++ нас не спасет (перевод)
Теги для поиска: #_programmirovanie (Программирование), #_java, #_kompiljatory (Компиляторы), #_ja_piarjus (я пиарюсь), #_jazyki_programmirovanija (языки программирования), #_programmirovanie (
Программирование
), #_java, #_kompiljatory (
Компиляторы
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 16:19
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
При разработке клиент-серверного приложения, у меня всегда появляется вопрос, а как я его буду разворачивать на сервере, упаковать его в jar/war/docker после написания кода, а потом еще надо передать на сервер, и еще много сделать телодвижений чтоб просто засунуть кусок кода на сервере.Было бы хорошо просто передать на сервер код, как лямбду, так же как мы передаем лямбду в функцию, так же на сервер ее передать.Но у меня появилась мысль как сделать этот процесс проще, и у меня что-то получилось. 1 | var query = TcpQuery
2 | .create(IEnv.class).host("myserver.com").port(9988) 3 | .build(); 4 | 5 | query.apply( 6 | env -> env.processes().stream().filter( 7 | p -> p.getName().contains("java") 8 | ) 9 | .collect(Collectors.toList()) 10| ).forEach(System.out::println);
public interface IEnv {
List<OsProc> processes(); } public class OsProc implements Serializable {
public Optional<Integer> getPpid(){ return ... } public int getPid(){ return ... } public void setPid(int pid){ ... } public String getName(){ return ... } public Optional<String> getCmdline(){ return ... } } var query = TcpQuery
.create(IEnv.class).host("myserver.com").port(9988) .build(); query.apply( env -> env.processes().stream().filter( p -> p.getName().contains("java") ) .collect(Collectors.toList()) ).forEach(System.out::println); При разработке клиентского приложения на Java
<dependency>
<groupId>xyz.cofe</groupId> <artifactId>trambda</artifactId> <version>1.0</version> <type>pom</type> </dependency> <dependency> <groupId>xyz.cofe</groupId> <artifactId>trambda-core</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>xyz.cofe</groupId> <artifactId>trambda-tcp</artifactId> <version>1.0</version> </dependency>
package xyz.cofe.trambda.demo.api;
import org.junit.jupiter.api.Test; public class LinuxEnvTest { @Test public void test(){ var env = new LinuxEnv(); env.processes().stream() .filter(p->p.getName().equalsIgnoreCase("java")) .forEach(System.out::println); } } LinuxEnv - это простой класс, он устроен так: package xyz.cofe.trambda.demo.api;
import java.util.ArrayList; import java.util.List; import xyz.cofe.io.fs.File; public class LinuxEnv implements IEnv { @Override public List<OsProc> processes(){ ArrayList<OsProc> procs = new ArrayList<>(); File procDir = new File("/proc"); procDir.dirList().stream() .filter( d -> d.getName().matches("\\d+") && d.isDir() ) .map(OsProc::linuxProc) .forEach(procs::add); return procs; } } > git clone https://github.com/gochaorg/trambda.git
Клонирование в «trambda»... remote: Enumerating objects: 978, done. remote: Counting objects: 100% (978/978), done. remote: Compressing objects: 100% (464/464), done. remote: Total 978 (delta 308), reused 862 (delta 195), pack-reused 0 Получение объектов: 100% (978/978), 715.70 KiB | 559.00 KiB/s, готово. Определение изменений: 100% (308/308), готово. user@user-Modern-14-A10RB:22:10:35:~/Загрузки/sample-tr:
> cd trambda/trambda-demo/tr-demo-api/ user@user-Modern-14-A10RB:22:10:49:~/Загрузки/sample-tr/trambda/trambda-demo/tr-demo-api: > mvn clean package install ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 13.933 s [INFO] Finished at: 2021-04-18T22:11:11+05:00 [INFO] ------------------------------------------------------------------------ user@user-Modern-14-A10RB:22:13:13:~/Загрузки/sample-tr/trambda/trambda-demo/tr-demo-api:
> ll target/ итого 48 drwxrwxr-x 10 user user 4096 апр 18 22:11 ./ drwxrwxr-x 4 user user 4096 апр 18 22:11 ../ drwxrwxr-x 3 user user 4096 апр 18 22:11 classes/ drwxrwxr-x 3 user user 4096 апр 18 22:11 generated-sources/ drwxrwxr-x 3 user user 4096 апр 18 22:11 generated-test-sources/ drwxrwxr-x 2 user user 4096 апр 18 22:11 maven-archiver/ drwxrwxr-x 3 user user 4096 апр 18 22:11 maven-status/ drwxrwxr-x 4 user user 4096 апр 18 22:11 site/ drwxrwxr-x 2 user user 4096 апр 18 22:11 surefire-reports/ drwxrwxr-x 3 user user 4096 апр 18 22:11 test-classes/ -rw-rw-r-- 1 user user 6337 апр 18 22:11 tr-demo-api-1.0-SNAPSHOT.jar user@user-Modern-14-A10RB:22:37:25:~/Загрузки/sample-tr:
> wget https://github.com/gochaorg/trambda/releases/download/1.0/trambda-tcp-serv-cli.zip --2021-04-18 22:37:31-- https://github.com/gochaorg/trambda/releases/download/1.0/trambda-tcp-serv-cli.zip Распознаётся github.com (github.com)... 140.82.121.4 Подключение к github.com (github.com)|140.82.121.4|:443... соединение установлено. HTTP-запрос отправлен. Ожидание ответа... 302 Found Адрес: https://github-releases.githubusercontent.com/350075998/47380d00-9b40-11eb-90a4-4e353f42e67c?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20210418%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210418T173731Z&X-Amz-Expires=300&X-Amz-Signature=97ade1f58bfbe1eaa320805179987e8c4df730b9f5eddf24c05662fb676caafe&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=350075998&response-content-disposition=attachment%3B%20filename%3Dtrambda-tcp-serv-cli.zip&response-content-type=application%2Foctet-stream [переход] --2021-04-18 22:37:31-- https://github-releases.githubusercontent.com/350075998/47380d00-9b40-11eb-90a4-4e353f42e67c?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20210418%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210418T173731Z&X-Amz-Expires=300&X-Amz-Signature=97ade1f58bfbe1eaa320805179987e8c4df730b9f5eddf24c05662fb676caafe&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=350075998&response-content-disposition=attachment%3B%20filename%3Dtrambda-tcp-serv-cli.zip&response-content-type=application%2Foctet-stream Распознаётся github-releases.githubusercontent.com (github-releases.githubusercontent.com)... 185.199.111.154, 185.199.108.154, 185.199.109.154, ... Подключение к github-releases.githubusercontent.com (github-releases.githubusercontent.com)|185.199.111.154|:443... соединение установлено. HTTP-запрос отправлен. Ожидание ответа... 200 OK Длина: 12107487 (12M) [application/octet-stream] Сохранение в: «trambda-tcp-serv-cli.zip» trambda-tcp-serv-cli.zip 100%[========================================================================================================================================>] 11,55M 3,75MB/s за 3,1s 2021-04-18 22:37:35 (3,75 MB/s) - «trambda-tcp-serv-cli.zip» сохранён [12107487/12107487] user@user-Modern-14-A10RB:22:37:35:~/Загрузки/sample-tr: > ll итого 11836 drwxrwxr-x 3 user user 4096 апр 18 22:37 ./ drwxr-xr-x 11 user user 4096 апр 18 22:00 ../ drwxrwxr-x 10 user user 4096 апр 18 22:10 trambda/ -rw-rw-r-- 1 user user 12107487 апр 12 03:36 trambda-tcp-serv-cli.zip user@user-Modern-14-A10RB:22:37:42:~/Загрузки/sample-tr: > unzip trambda-tcp-serv-cli.zip Archive: trambda-tcp-serv-cli.zip creating: trambda-tcp-serv-cli/ creating: trambda-tcp-serv-cli/jars/ inflating: trambda-tcp-serv-cli/jars/asm-9.1.jar inflating: trambda-tcp-serv-cli/jars/jline-2.14.6.jar inflating: trambda-tcp-serv-cli/jars/iofun-1.0.jar inflating: trambda-tcp-serv-cli/jars/groovy-swing-3.0.7.jar inflating: trambda-tcp-serv-cli/jars/groovy-console-3.0.7.jar inflating: trambda-tcp-serv-cli/jars/groovy-xml-3.0.7.jar inflating: trambda-tcp-serv-cli/jars/trambda-tcp-serv-cli-1.0.jar inflating: trambda-tcp-serv-cli/jars/ecolls-1.10.jar inflating: trambda-tcp-serv-cli/jars/trambda-core-1.0.jar inflating: trambda-tcp-serv-cli/jars/slf4j-api-1.7.25.jar inflating: trambda-tcp-serv-cli/jars/asm-tree-9.1.jar inflating: trambda-tcp-serv-cli/jars/asm-util-9.1.jar inflating: trambda-tcp-serv-cli/jars/fs-1.2.jar inflating: trambda-tcp-serv-cli/jars/logback-classic-1.2.3.jar inflating: trambda-tcp-serv-cli/jars/trambda-tcp-1.0.jar inflating: trambda-tcp-serv-cli/jars/groovy-groovysh-3.0.7.jar inflating: trambda-tcp-serv-cli/jars/groovy-templates-3.0.7.jar inflating: trambda-tcp-serv-cli/jars/asm-analysis-9.1.jar inflating: trambda-tcp-serv-cli/jars/text-1.0.jar inflating: trambda-tcp-serv-cli/jars/logback-core-1.2.3.jar inflating: trambda-tcp-serv-cli/jars/groovy-3.0.7.jar inflating: trambda-tcp-serv-cli/jars/cbuffer-1.3.jar creating: trambda-tcp-serv-cli/bin/ inflating: trambda-tcp-serv-cli/bin/trambda-tcp-serv.bat inflating: trambda-tcp-serv-cli/bin/trambda-tcp-serv user@user-Modern-14-A10RB:22:37:50:~/Загрузки/sample-tr: > rm trambda-tcp-serv-cli.zip ser@user-Modern-14-A10RB:22:40:47:~/Загрузки/sample-tr:
> cp -v trambda/trambda-demo/tr-demo-api/target/tr-demo-api-1.0-SNAPSHOT.jar trambda-tcp-serv-cli/jars/ 'trambda/trambda-demo/tr-demo-api/target/tr-demo-api-1.0-SNAPSHOT.jar' -> 'trambda-tcp-serv-cli/jars/tr-demo-api-1.0-SNAPSHOT.jar' > cat trambda/trambda-tcp-serv-cli/src/test/samples/sample-1.groov
// Сервис xyz.cofe.trambda.demo.api.LinuxEnv
// будет запущен на порту 9988, и будет доступен с любого IP app.service( "0.0.0.0:9988", new xyz.cofe.trambda.demo.api.LinuxEnv() ) { daemon false // Указываем настройки безопасности security { // Какие API/Методы будут доступны извне allow { // method("System") { // methodOwner ==~ /java.lang.System/ && methodName in ['gc'] // } // field( "System.out" ) { // fieldOwner ==~ /java.lang.System/ && fieldName in ['out','in','err'] && readAccess // } invoke( 'Java compiler' ){ methodOwner ==~ /java\.lang\.invoke\.(LambdaMetafactory|StringConcatFactory)/ } invoke( 'Java collections' ){ methodOwner ==~ /java\.util\.(stream\.(Stream|Collectors)|(List))/ } invoke( 'Java lang' ){ methodOwner ==~ /java\.lang\.String/ } invoke( 'Api '){ methodOwner ==~ /xyz\.cofe\.trambda\.demo\.api\.(IEnv|OsProc)/ } } // Для всех остальных случаев - запрещаем вызов deny { any("ban all") } } } user@user-Modern-14-A10RB:22:56:08:~/Загрузки/sample-tr:
> bash ./trambda-tcp-serv-cli/bin/trambda-tcp-serv -s trambda/trambda-tcp-serv-cli/src/test/samples/sample-1.groovy # [main] INFO x.c.t.tcp.serv.cli.TcpServerCLI - starting xyz.cofe.trambda.tcp.serv.cli.TcpServerCLI # [main] INFO x.c.t.tcp.serv.cli.TcpServerCLI - executeScript( "trambda/trambda-tcp-serv-cli/src/test/samples/sample-1.groovy", UTF-8 ) # [main] INFO x.c.t.tcp.serv.cli.TcpServerCLI - registry class xyz.cofe.trambda.demo.api.LinuxEnv on 0.0.0.0:9988 # [main] INFO x.c.t.tcp.serv.cli.TcpServerCLI - starting service xyz.cofe.trambda.demo.api.LinuxEnv@55e7a35c on /0.0.0.0:9988 # [main] DEBUG x.c.t.tcp.serv.cli.TcpServerCLI - create server socket # [main] DEBUG x.c.t.tcp.serv.cli.TcpServerCLI - bind server socket /0.0.0.0:9988 # [main] DEBUG x.c.t.tcp.serv.cli.TcpServerCLI - server started package xyz.cofe.trambda.demo.client;
import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import xyz.cofe.trambda.demo.api.IEnv; import xyz.cofe.trambda.tcp.TcpQuery; public class ClientTest { @Test public void test01(){ var query = TcpQuery .create(IEnv.class).host("localhost").port(9988) .build(); query.apply( env -> env.processes().stream().filter(p -> p.getName().contains("java")) .collect(Collectors.toList()) ).forEach(System.out::println); } } Что происходит на сервере ?часть логов я опущу - т.к. для пояснения работы они не важны Настройка безопасности сервераСовсем не обязательно использовать сервер через скрипты запуска, сервер можно встроить как часть своего приложения и настраивать так: // Создание TCP сокета
ServerSocket ssocket = new ServerSocket(port); // Настраиваем сокет ssocket.setSoTimeout(1000*5); // Создаем сервер server = new TcpServer<IEnv>( // Передаем сокет ssocket, // Передаем функцию получения сервиса для новой сессии s -> new LinuxEnv(), // Настраиваем безопасность SecurityFilters.create(s -> { // Разрешаем вызовы строго - определенных методов s.allow( a -> { // Публикуемый API нашего сервиса a.invoke("demo api", c-> c.getOwner().matches( "xyz\\.cofe\\.trambda\\.tcp\\.demo\\.([\\w\\d]+)")); // Работа с коллекциями a.invoke("java collections api", c->c.getOwner().matches( "java\\.util\\.(List)|java\\.util\\.stream\\.([\\w\\d]+)")); // Работа с Java строками a.invoke("java lang api", c-> c.getOwner().matches("java\\.lang\\.(String)")); // Методы которые использует компилятор Java a.invoke("java compiler", c-> c.getOwner().matches( "java\\.lang\\.invoke\\.(LambdaMetafactory|StringConcatFactory)")); }); // Все остальное запрещаем s.deny().any("by default"); }) ); // Указываем что Thread сервера будет запущен как фоновый server.setDaemon(true); // Запускаем сервер server.start();// Создание TCP сокета ServerSocket ssocket = new ServerSocket(port); // Настраиваем сокет ssocket.setSoTimeout(1000*5);
package xyz.cofe.trambda.l1;
import java.util.function.Function; import org.junit.jupiter.api.Test; public class SimpleLambdaTest { @Test public void javaLambda01(){ Function<Function<String,String>,String> test = (f) -> { System.out.println("f="+f.getClass()); return null; }; test.apply( x -> x.repeat(4) ); } } user@user-Modern-14-A10RB:00:41:32:~/code/trambda/trambda-core/target/test-classes/xyz/cofe/trambda/l1:
> ll итого 12 drwxrwxr-x 2 user user 4096 апр 25 00:40 ./ drwxrwxr-x 5 user user 4096 апр 25 00:40 ../ -rw-rw-r-- 1 user user 2162 апр 25 00:40 SimpleLambdaTest.class > javap -p SimpleLambdaTest.class
Compiled from "SimpleLambdaTest.java" public class xyz.cofe.trambda.l1.SimpleLambdaTest { public xyz.cofe.trambda.l1.SimpleLambdaTest(); public void javaLambda01(); private static java.lang.String lambda$javaLambda01$1(java.lang.String); private static java.lang.String lambda$javaLambda01$0(java.util.function.Function); } package xyz.cofe.trambda.l2;
import java.io.Serializable; import java.util.function.Function; public interface Fn<A,Z> extends Function<A,Z> , Serializable { } Runnable r = (Runnable & Serializable)() -> System.out.println("Serializable!");
package xyz.cofe.trambda.l2;
import java.lang.invoke.SerializedLambda; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Function; import org.junit.jupiter.api.Test; public class SerialLambdaTest { @Test public void serLambda01(){ Fn<Fn<String,String>,String> test = (lambda) -> { System.out.println("lambda="+lambda.getClass()); Method writeReplace = null; try{ writeReplace = lambda.getClass().getDeclaredMethod("writeReplace"); writeReplace.setAccessible(true); SerializedLambda sl = (SerializedLambda) writeReplace.invoke(lambda); System.out.println(sl); } catch( NoSuchMethodException | InvocationTargetException | IllegalAccessException e ) { e.printStackTrace(); } return null; }; test.apply( x -> x.repeat(4) ); } } lambda=class xyz.cofe.trambda.l2.SerialLambdaTest$$Lambda$235/0x0000000800142040
SerializedLambda[capturingClass=class xyz.cofe.trambda.l2.SerialLambdaTest, functionalInterfaceMethod=xyz/cofe/trambda/l2/Fn.apply:(Ljava/lang/Object;) Ljava/lang/Object;, implementation=invokeStatic xyz/cofe/trambda/l2/SerialLambdaTest.lambda$serLambda01$3fed5817$1: (Ljava/lang/String;)Ljava/lang/String;, instantiatedMethodType=(Ljava/lang/String;)Ljava/lang/String;, numCaptured=0] user@user-Modern-14-A10RB:00:51:48:~/code/trambda/trambda-core/target/test-classes/xyz/cofe/trambda/l2:
> javap -p SerialLambdaTest.class Compiled from "SerialLambdaTest.java" public class xyz.cofe.trambda.l2.SerialLambdaTest { public xyz.cofe.trambda.l2.SerialLambdaTest(); public void serLambda01(); private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda); private static java.lang.String lambda$serLambda01$3fed5817$1(java.lang.String); private static java.lang.String lambda$serLambda01$47b6c34$1(xyz.cofe.trambda.l2.Fn); }
SerializedLambda sl = (SerializedLambda)writeReplace.invoke(lambda);
var implClassName = sl.getImplClass() var implClassUrl = labmda.getClass().getResource("/"+implClassName.replace(".","/")+".class"); byte[] classByteCode = null;
try{ classByteCode = IOFun.readBytes(implClassUrl); } catch( IOException e ) { throw new IOError(e); } var classReader = new ClassReader(classByteCode);
ClassVisitor cv = new ClassVisitor(Opcodes.ASM9) {
@Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { if( methName.equals(name) && descriptor!=null && descriptor.equals(methSign) ){ mdef0.set(new MethodDef(access,name,descriptor,signature,exceptions)); return dump(byteCodes::add); } return null; } }; cr.accept(cv, 0); package xyz.cofe.trambda;
public class MethodDump extends MethodVisitor implements Opcodes { ... @Override public void visitParameter(String name, int access){ emit(new MParameter(name,access)); } @Override public void visitInsn(int opcode){ emit(new MInsn(opcode)); } ... } public synchronized byte[] generate(){
// генерируем имя целевого класса binClassName = className.replace('.', '/'); // Создаем ClassWriter (часть ASM) // в котором будем вызывать методы visitXXXX( op ) var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC|Opcodes.ACC_SUPER, binClassName,null, "java/lang/Object", null ); // генерация метода var mv = cw.visitMethod( acc, // флаги как static public name, // имя метода desc, // параметры метода sign, // сигнатура если есть Generic параметры excepts); // исключения которые может генерировать метод // Потом в цикле для каждого переданного объекта // генерируем соответствующий вызов visitXXXX() // генерируем код в той же последовательности, что и был прочитан for( var bc : byteCodes ){ if( bc instanceof MCode )build((MCode) bc); else if( bc instanceof MEnd )build((MEnd) bc); else if( bc instanceof MLabel )build((MLabel) bc); else if( bc instanceof MLineNumber )build((MLineNumber) bc); else if( bc instanceof MVarInsn )build((MVarInsn) bc); ... } // Получаем байт код return cw.toByteArray(); } // Вызов visitCode() - начало метода protected void build(MCode code){ mv.visitCode(); } // Вызов visitEnd() - конец метода protected void build(MEnd end){ mv.visitEnd(); } protected void build(MTypeInsn tinst){ mv.visitTypeInsn(tinst.getOpcode(), tinst.getOperand()); } var byteCode = new MethodRestore()
.className(clName) .methodName("lambda1") .methodDef(mdef) .generate(); ClassLoader cl = new ClassLoader(ClassLoader.getSystemClassLoader()) { @Override protected Class<?> findClass(String name) throws ClassNotFoundException{ if( name!=null && name.equals(clName) ){ return defineClass(name,byteCode,0,byteCode.length); } return super.findClass(name); } }; System.out.println("try read class "+clName);
Class c = null; try{ c = Class.forName(clName,true,cl); System.out.println("class found "+c); } catch( ClassNotFoundException e ) { e.printStackTrace(); return; } Method m = null;
System.out.println("methods"); for( var delMeth : c.getDeclaredMethods() ){ System.out.println(""+delMeth); if( delMeth.getName().equals(methName) ){ m = delMeth; } } try{
Object arg0 = "abc"; System.out.println("call with "+arg0); Object res = m.invoke(null, arg0); System.out.println("result "+res); } catch( IllegalAccessException | InvocationTargetException e ) { e.printStackTrace(); }
=========== Источник: habr.com =========== Похожие новости:
Программирование ), #_java, #_kompiljatory ( Компиляторы ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 16:19
Часовой пояс: UTC + 5