Управление конфиденциальностью в большой программе
Я работаю над программой для робототехники с этой структурой (я включу то, что слои делают только для смеха):
Уровень A - простой графический интерфейс пользователя для управления кнопками, джойстиками и т. Д. Переводит эти значения в команды для отправки в интерфейс управления.
Уровень B - Control оценивает записи устройства на чтение / запись из базы данных и запросы команд от графического интерфейса пользователя, чтобы вычислить новые записи устройства на запись для базы данных.
Уровень C - База данных логического представления устройств, создание истории значений, записанных и прочитанных с устройств
Уровень D - Аппаратные средства взаимодействуют с физическим оборудованием. Преобразует записи устройства-записи в команды и отправляет их на устройства. Обновляет базу данных device-read-records со значениями от устройств.
Я хочу создать Java-приложение, в котором ни один слой не может вызывать функции, которые находятся на несколько уровней выше или ниже его.
Можно ли создать структуру проекта с использованием конфиденциальности пакетов или таких шаблонов, как фабрики, которые делают невозможным для кода, скажем, на уровне A, импортировать что-либо из слоев D или C?
2 ответа
Это не то, чего вы можете достичь, используя только модификаторы доступа.
Вы также не можете сделать это (каким-то образом) контролируя import
... потому что язык Java не накладывает никаких (дополнительных) ограничений на импорт. (The import
Директива на самом деле просто синтаксический сахар, так что вам не нужно везде использовать полностью определенные имена.)
Так что еще ты мог сделать?
Вы можете попытаться реализовать ограничения во время выполнения, чтобы предотвратить доступ неправильного слоя к фабричным объектам. Но такие ограничения легко подрываются, умышленно или случайно.
Вы можете использовать какой-то внутренний механизм "возможностей" или "учетных данных", но трудно понять, как можно предотвратить утечку учетных данных. (Если учетными данными управлял менеджер безопасности (см. Ниже), это может сработать, но это усложняет проблему.)
Я думаю, что единственный способ сделать это - реализовать SecurityManager
и осуществлять проверки безопасности каждый раз, когда возникает потенциально возможный переход уровня. Например, менеджер безопасности может (хотя и дорого) проверить стек вызовов, чтобы определить, какой метод / класс / пакет вызвал его. Вам также необходимо отключить некоторые отражающие операции, которые можно использовать (тривиально) для подрыва менеджера безопасности. По сути, все, кроме внутреннего кольца, должны рассматриваться как "недоверенный" код.
Честно говоря, реализация такого рода вещей с помощью JVM с "защищенной от хакеров" безопасностью, вероятно, недоступна смертному человеку. (Sun / Oracle пока не преуспели....)
Другие альтернативы:
Положитесь на дисциплину программиста.
Положитесь на статический анализ кодовой базы; например, с помощью аннотаций, которые документируют правила доступа. Для этого вам нужно написать собственный анализатор кода.
Используйте разделение адресного пространства и небольшие, защищенные API-интерфейсы между уровнями. (Мы больше не говорим об одной обычной JVM здесь...)
TL;DR нет единого решения для волшебной пули, но есть много разных инструментов для использования
Существует множество различных методов для изоляции различных частей программного приложения, но я не думаю, что есть какое-то одно решение, которое решает все. Некоторые системы сборки могут ограничивать зависимости между целями (например, Bazel имеет visibility
свойство целевых объектов сборки, которое может помешать одной цели зависеть от другой цели, даже если они видны друг другу через видимость классов Java), которое можно использовать вместе со встроенной видимостью Java. Например:
// Foo.java
package com.yourcompany.foo;
public class Foo {}
// Build rule for Foo.java
java_library(
name = "Foo",
srcs = ["Foo.java"],
# Restricts visibility to this directory, even though
# the class visibility was "public"
visibility = ["//visibility:private"],
)
// Bar.java
package com.yourcompany.bar;
import com.yourcompany.foo.Bar; // prevented by build visibility system
public class Bar {
Foo foo = new Foo();
}
Также возможно использовать интерфейсы для передачи всех взаимодействий между логическими компонентами и для скрытия реализаций этих интерфейсов (например, предоставление реализации только через интерфейс реестра служб или посредством внедрения зависимости интерфейса). Например, с Dagger вы можете создать отдельный компонент для каждого слоя, который позволит вам писать код, подобный следующему:
final class ControllerImpl implements Controller {
// Since "ControllerImpl" is instantiated / wired into the
// controller layer, the database dependency is available /
// exposed for injection within this layer. The access control is
// strictly performed by the way the dependencies are wired.
@Inject
public ControllerImpl(Database database) {
// ...
}
}
В дополнение к вышесказанному вы можете использовать тесты анализа зависимостей / анализа зависимостей или фиксировать фиксации для автоматизации обнаружения нарушений правил зависимости (и инициировать ошибки / отклонять представления на их основе). Например, решение для бедного человека состоит в том, чтобы просто сканировать каждый файл на предмет объявления пакета и операторов импорта, а затем использовать некоторую эвристику для обнаружения плохой зависимости.
Другой подход заключается в объединении различных компонентов в отдельные JAR-файлы и загрузке их с помощью пользовательского ClassLoader, который позволит вам предотвратить несанкционированный доступ с использованием отражения (что в противном случае могло бы обойти любую программную структуру).
В дополнение к автоматизированным методам, ручной подход также имеет свое значение. Подходы вручную включают в себя регулярные проверки кода и политики, которые должны применяться во время этих проверок и аудита кода.
Короче говоря, нет единственно правильного ответа. Необходимо использовать несколько разных подходов совместно, в зависимости от того, насколько критично это разделение.