Аннотация JS — это набор аннотационных фреймворков, реализованных на основе предложения аннотаций в JavaScript proposal-decorators
. С помощью этого фреймворка можно реализовать такие вещи, как внедрение зависимостей, как в Java, а также программирование с использованием аспектов, что применимо к серверам Node.js и обычному JavaScript-приложениям.
Примечание: Предложение
proposal-decorators
было значительно изменено и пока не окончательно принято, поэтому неизвестно, будет ли синтаксис меняться снова. Из-за особенностей нашего фреймворка и необходимости использования Babel, мы используем старую версию синтаксиса декораторов, требуя конфигурацию"legacy": true
. В будущем, когда предложение будет окончательно принято, мы будем переоценивать возможность его применения и решим, следует ли перестроить основные части фреймворка.
Однако не стоит беспокоиться, так как благодаря Babel, даже если новый синтаксис уже доступен, наш фреймворк всё равно будет работать корректно.
Наконец, прилагаются руководства по использованию синтаксиса, используемые этим фреймворком:· Декораторы - ECMAScript 6 для начинающих (внутренний источник)
· javascript-decorators
npm install @palerock/annotate-js
Подготовьте проект с поддержкой декораторов и некоторыми другими возможностями JavaScript, такими как:
// Конфигурационный файл .babelrc для Babel 7
{
"presets": [
[
"@babel/preset-env"
]
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
]
]
}
Используйте аннотации @Bean
, @Boot
, @Autowired
для внедрения зависимостей
import { Bean, Boot, Autowired } from '@palerock/annotate-js';
/**
* Объявление компонента с названием Demo
*/
@Bean
class Demo {
sayHello() {
console.log('hello annotate-js.');
}
}
```@Boot
class Init {
@Autowired
Demo; // Автоматическое внедрение
// Входная точка программы
main() {
this.Demo.sayHello(); // вывод `hello annotate-js.`
}
}
Используйте @Section
для реализации программирования с использованием аспектов, модифицировав класс Demo следующим образом:
import {Bean, Section} from '@palerock/annotate-js';
/**
* Объявление компонента с названием Demo
*/
@Bean
class Demo {
@Section({
before() {
console.log('перед вызовом.');
},
after() {
console.log('после вызова.');
}
})
sayHello() {
console.log('hello annotate-js.');
}
// Другой код здесь...
}
При выполнении программа выведет три строки текста:
перед вызовом.
hello annotate-js.
после вызова.
Через встроенные аннотации можно создать простые аннотации @Annotate
, @DefaultParam
и @DynamicParam
:
import {
Annotate,
BeanDescribe,
Boot,
DefaultParam,
DynamicParam,
EnergyWire,
SectionDescribe
} from "@palerock/annotate-js";
// Объявление аннотации ConfigurableBean, которая наследует BeanDescribe, то есть @Bean
// Изменение параметров через определение членов класса
@Annotate({extends: BeanDescribe})
class ConfigurableBean {
// Установка аргументов как по умолчанию со значением [1,3,4]
@DefaultParam
args = [1, 3, 4];
// Изменение параметра name на динамический параметр, который получает значение методом, динамически определяющим результат
// Здесь динамически получаем имя декорируемого класса и приводим его к нижнему регистру
@DynamicParam
name({classEntity}) {
return classEntity.name.toLowerCase();
}
}}
@Annotate({extends: SectionDescribe})
class ParamsAppendMethodName {
// Динамическое добавление имени метода к первому параметру при вызове метода, декорированного данной аннотацией
@DynamicParam
before({propertyEntity}) {
return ({params}) => {
params[0] = params[0] + ' - от метода: ' + propertyEntity.name;
}
}
}
@ConfigurableBean // Тестирование значения по умолчанию
class TestBean {
constructor(...args) {
this.args = args;
}
}
@ConfigurableBean([3, 4, 5]) // Тестирование значения по умолчанию для параметра args
class TestBean1 {
constructor(...args) {
this.args = args;
}
}
@ParamsAppendMethodName # проверяем, были ли методы с параметрами добавлены с суффиксом имени метода sayHello(word) { console.log('привет', word); } }
@Boot class Application {
/**
* Динамическое внедрение аргументов и методов
*/
@EnergyWire('TestBean')
args;
@EnergyWire('TestBean1.args')
args1;
@EnergyWire('TestBean1')
sayHello;
main() {
console.log(this.args);
console.log(this.args1);
this.sayHello('мир');
}
}
Запустив вышеуказанный код, вывод будет следующим:
Decorator type [_AnonymousDescribe] работает на бине [testbean] Decorator type [_AnonymousDescribe] работает на бине [testbean1] Decorator type [BootDescribe] работает на бине [bootBean] [ 1, 3, 4 ] [ 3, 4, 5 ] привет мир - из метода: sayHello
Как видно, все `beanName` преобразованы в нижний регистр, а также значения по умолчанию и параметры выводятся корректно. Внедрение суффиксов имён методов также работает правильно.
Чтобы создать новые аннотации на основе существующих, можно использовать следующий подход:Используйте `BeanDescribe` и `PropertyDescribe` через `AnnotationGenerator`, чтобы создать пользовательскую аннотацию:
1. `BeanDescribe` применим для объявления на уровне класса, генерируемый аннотация по умолчанию совпадает с `@Bean`.
2. `PropertyDescribe` применим для объявления на уровне свойства или метода, по умолчанию никакой функциональности не имеет.
```javascript
// пример использования AnnotationGenerator
import {BeanDescribe, PropertyDescribe, Boot, AnnotationGenerator} from '@palerock/annotate-js';
class MyBeanDescribe extends BeanDescribe {
constructor() {
super();
Object.assign(this.params, {
// здесь можно добавлять или изменять параметры аннотации по умолчанию
});
}
onCreated() {
super.onCreated();
// когда класс, помеченный этой аннотацией, и его свойства инициализированы
}
/**
* регистрация Proxy
* @param proxy используется как показано ниже
*/
proxyRegister(proxy) {
super.proxyRegister(proxy);
// это вызывается при регистрации proxy в цепочке proxy, если все предыдущие прокси вызвали next
proxy.register('get', ([...args], {next}) => {
if (!Reflect.get(...args)) {
return 'Привет от Прокси';
}
next() // обязательно вызвать next, иначе все непустые свойства вернут пустое значение
})
}
}
}
class MyPropertyDescribe extends PropertyDescribe {
constructor() {
super();
Object.assign(this.params, {
// Здесь можно настроить значения по умолчанию для аннотаций
tip: ''
});
}
}
``` /**
* Этот метод применяется ко всем свойствам, помеченным аннотациями.
* @param proxy - регистратор, подробнее см. пример ниже.
* @param container {BasicBeanContainer} - контейнер для хранения всех бинов, через который можно получить доступ к любому бину по имени.
*/
hookProperty({proxy, container}) {
super.hookProperty({proxy, container});
}```markdown
proxy.register('get', ([target, property, receiver], {next}) => {
// Здесь можно перехватывать метод get для каждого свойства
const realValue = Reflect.get(target, property, receiver);
const params = this.params;
if (typeof realValue === 'function') {
// Если реальное значение является функцией, передаем параметры аннотации в эту функцию
return function (args = {}) {
args.annotateParams = params;
return realValue.bind(this)(args);
};
}
next(); // Если условие не выполняется, вызываем следующий перехватчик
});
}
/**
* Этот метод вызывается после полной генерации класса
* Можно использовать для выполнения действий после создания класса или получения значений аннотаций
* @param propertyEntity{PropertyEntity} - объект свойства, см. /main/core/entities/PropertyEntity
* @param classDecorator{BeanDescribe} - описание аннотации, примененной к классу
*/
onClassBuilt(propertyEntity, classDecorator) {
super.onClassBuilt(propertyEntity, classDecorator);
// Определяется в производном классе.
}
}
const MyBean = AnnotationGenerator.generate(MyBeanDescribe);
const MyProperty = AnnotationGenerator.generate(MyPropertyDescribe);
@Boot
@MyBean
class BootEntry {
finalProxyValue;
}
``````markdown
main() {
// Входная точка
// Обратите внимание, что здесь не передаются параметры annotateParams и tip
const result = this.test(); // вывод: `Testing My Property`
console.assert(result === 'Testing My Property');
console.assert(this.finalProxyValue === 'Hello Proxy');
}
}
Требования к окружению: только проекты с серверной частью на NodeJS
В окружении NodeJS можно использовать Scanner для сканирования всех файлов JavaScript в проекте для загрузки нескольких компонентов из нескольких файлов JavaScript
Пример:
import BasicScanner from "@palerock/annotate-js";
``````markdown
new BasicScanner().setContext(__dirname).scan(
['./examples/01-basic/*'] // путь к проекту, * — это шаблон
);
@Bean
Эта аннотация используется для создания компонента.
Область применения: только класс.
Параметры:
name
String
args
Array<Object>
[]
isSectionSurround
Boolean
true
true
, компонент автоматически распознает аннотацию @Section
.containerType
Class
BasicBeanContainer
```javascript
@Bean
class MyComponent {
// Нет параметров, все используются по умолчанию, name = 'MyComponent'
}
@Bean('MyCmp')
class MyComponent1 {
// Только один параметр установлен, по умолчанию присваивается name, name = 'MyCmp'
}
@Bean({
name:'CmpTest',
args:[123]
})
class MyComponent2 {
// Установлено несколько параметров
constructor(value) {
console.log(value); // 123
}
}
@Boot
Эта аннотация используется для определения точки входа программы.
Функционал и параметры наследуются от @Bean
.
По умолчанию: name
.
Изменение/дополнительные параметры:
name
String
'bootBean'
name
.methodName
String
'main'
@Property
Эта аннотация используется для декорирования членских свойств; членские свойства, декорируемые этой аннотацией, могут быть распознаны `@Bean`. Для создания пользовательской аннотации для декорирования членских свойств рекомендуется использовать эту аннотацию в качестве Describer. Область применения: `Класс` или `Свойство`.
Параметры:
prioritet
Число
0
@Bean
. Чем больше значение, тем выше приоритет.@Autowired
Эта аннотация наследуется от @Property
Используется для внедрения указанного Bean
или его конкретного свойства в членскую переменную, декорированную данной аннотацией.
Область применения: только свойство
.
По умолчанию: beanName
Новый параметр:
name
строка
isMapProperty
не равен true
, он совпадает со значением beanName
; в противном случае этот параметр выполняет ту же роль, что и propertyName
.beanName
строка
name
Bean
, которое должно быть внедрено. Указание этого параметра позволяет получить компонент, соответствующий этому свойству, среди всех объявленных Beans.isMapProperty
логическое значение
false
bean
. Эффективно работает только тогда, когда параметры propertyName
или name
имеют значения.propertyName
строка
''
isMapProperty
имеет значение true
. Этот параметр указывает имя свойства внутри bean
, которое требуется внедрить.@EnergyWire
Функциональность и параметры наследуются от @Autowired
.
Особое внимание следует обратить на то, что эта аннотация сосредоточена на внедрении конкретного свойства внутри bean
.По умолчанию: beanName
Изменения/дополнительные параметры:
name
String
beanName
, если isMapProperty
не равно true
. В противном случае этот параметр может быть указан как цепочка строк для представления beanName
+ propertyName
.'MyBean.MyProperty'
beanName
String
name
Bean
, которое требуется внедрить. Указание этого параметра позволяет получить компонент, соответствующий этому свойству, среди всех объявленных Beans
.A.B
, где A
представляет собой beanName
, а B
— propertyName
.isMapProperty
true
, изменения недействительныpropertyName
String
bean
, который требуется внедрить.Пример простого использования:
@Bean
class APIService {
Корректировка:
По умолчанию: `beanName`
Изменения/дополнительные параметры:
- `name`
- Тип: `String`
- По умолчанию: имя декорируемой переменной
- Описание: совпадает с `beanName`, если `isMapProperty` не равно `true`. В противном случае этот параметр может быть указан как цепочка строк для представления `beanName` + `propertyName`.
- Примечание: максимальная глубина цепочки — два уровня.
- Пример: `'MyBean.MyProperty'`
- Необязательный параметр
- `beanName`
- Тип: `String`
- По умолчанию: значение параметра `name`
- Описание: имя `Bean`, которое требуется внедрить. Указание этого параметра позволяет получить компонент, соответствующий этому свойству, среди всех объявленных `Beans`.
- Новое: данный параметр поддерживает цепочку выражений, например `A.B`, где `A` представляет собой `beanName`, а `B` — `propertyName`.
- Необязательный параметр
- По умолчанию: если в аннотации один параметр и он не является объектом, то параметр, помеченный как по умолчанию, будет присвоен этому значению.
- `isMapProperty`
- Всегда равен `true`, изменения недействительны
- Необязательный параметр
- `propertyName`
- Тип: `String`
- По умолчанию: имя декорируемого свойства
- Описание: указывает имя члена `bean`, который требуется внедрить.
- Необязательный параметр
**Пример простого использования:**
```java
@Bean
public class APIService {}
api01() {
console.log('api 01 вызвано', this.API_KEY);
}
api02() {
console.log('api 02 вызвано', this.API_KEY);
}
}
@Boot
class TestBoot {
@EnergyWire('APIService')
api01;
@EnergyWire({
beanName: 'APIService',
propertyName: 'api02'
})
api02;
@EnergyWire('APIService.api02')
api03;
main() {
this.api01(); // api 01 вызвано API Key
this.api02(); // api 02 вызвано API Key
this.api03(); // api 02 вызвано API Key
}
}
@Section
Эта аннотация наследуется от @Property
и должна использоваться вместе с @Bean
.
Область применения: класс
или свойство
, обратите внимание, что это работает только для свойств типа Функция
.
Когда аннотация применяется к класс
, это означает, что все свойства этого класса также используют эту аннотацию.По умолчанию параметр priority
действует так же, как в родительском элементе. Добавленные параметры:
before
Функция
Функция
Объект - {ключ:значение}
Массив<Объект>
@Section
, внешний Section
может получить последнюю вызванную функцию Surround
isSectionSurround
в аннотации @Bean
равном false
Логический тип
является ли текущий метод getter'омЛогический тип
является ли текущий метод setter'омОбъект
текущий объект аннотацииОбъект
представляет собой кэшированный объект текущего контекста, изменения которого могут быть получены методами after
или onError
Функция
если эта функция будет вызвана, выполнение исходной функции будет заблокировано, и значение, возвращаемое функцией before
, будет использоваться как возвращаемое значение исходной функцииafter
функция
функция
объект - {ключ:значение}
массив<объект>
@Section
, внешний Section
может получить последнюю вызванную функцию Surround
lastOrigin
isSectionSurround
в аннотации @Bean
равном false
логический тип
является ли текущий метод getter'омлогический тип
является ли текущий метод setter'омобъект
текущий объект аннотацииобъект
представляет собой кэшированный объект текущего контекста, изменения которого могут быть получены методами after
или onError
@Section
, то lastValue
будет передана следующему Section
onError
- Тип: Функция
Функция - result
@Bean
свойство isSectionSurround == false
Объект - {ключ:значение}
Массив<Объект>
@Surround
, внешний Surround
может получить последнюю вызванную Surround
функциюЛогическое значение
является ли текущий метод getterЛогическое значение
является ли текущий метод setterОбъект
текущий объект аннотацииОбъект
представление кэшированной переменной текущего транзакта, изменение этого значения позволяет методам after
или onError
получать изменённое значениеФункция
вызов этой функции приведёт к прекращению выполнения исходного метода, и передаче значения возврата метода before как значения возврата исходного метода - Возвращаемое значение: если аргумент resolve пустой, используется это возвращаемое значениеisAsync
Логическое значение
'false'
@Section
асинхронным (работает только при условии bean
свойство isSectionSurround == false
). В других случаях автоматически определяется, является ли он асинхронным.@Section
применяются к одному свойству, и хотя бы одна из этих аннотаций имеет isAsync = true
, то все аннотации @Section
будут выполняться асинхронно.@Surround
Функционал аналогичен @Section
, но данная аннотация не зависит от аннотации @Bean
и действует в любых случаях.
Область применения: метод
или геттер/сеттер
.
Примечание: Избегайте декорирования одинаковым образом обоих методов геттера и сеттера с одним и тем же именем данной аннотацией, так как для одного и того же имени геттера/сеттера
аннотация будет считаться одной и той же свойственной характеристикой.
Неправильный пример:
class A {
@Surround
get name() {
return this._name;
}
@Surround
set name(value) {
this._name = value;
}
}
Правильный пример:
class A {
get name() {
return this._name;
}
@Surround
set name(value) {
this._name = value;
}
}
или
class A {
@Surround
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
}
```Значение по умолчанию: `before`
Параметры:
- `before`
- Тип: `Функция`
- По умолчанию: пустая функция
- Описание: Вызывается перед выполнением метода, декорированного данной аннотацией
- Коллбэк параметры: (объектная форма)
- params: параметры вызова исходного метода — `Объект - {ключ:значение}`
- annotations: список аннотаций текущего метода — `Массив<Объект>`
- propertyEntity: сущность свойства, содержащая информацию о имени свойства и т. д.
- lastOrigin: если есть несколько аннотаций `@Surround`, внешний `Surround` может получить последнюю выполненную `Surround` функцию
- isGetter: `Логическое значение` является ли текущий метод геттером
- isSetter: `Логическое значение` является ли текущий метод сеттером
- annotate: `Объект` текущий объект аннотации
- trans: `Объект` представляет собой кэшированный переменный текущего транзакта, изменения этого переменного позволяют следующим выполняющимся методам `after` или `onError` получать изменённое значение
- preventDefault: `Функция` если эта функция будет вызвана, то выполнение исходного метода будет заблокировано, а возвращаемое значение `before` станет значением возврата исходного метода
- Возвращаемое значение: нет специального назначения
- Не обязательный
- По умолчанию: если один параметр аннотации не является объектом, он будет присвоен значению по умолчанию
- `after`
- Тип: `Функция` - По умолчанию: пустая функция
- Описание: вызывается после завершения выполнения метода, декорированного данной аннотацией
- Коллбэк параметры: (объектная форма)
- params: параметры вызова исходного метода - `Объект - {ключ:значение}`
- annotations: список аннотаций текущего метода - `Массив<Объект>`
- propertyEntity: сущность свойства, содержащая информацию о имени свойства и т. д.
- lastOrigin: если есть несколько аннотаций `@Surround`, внешний `Surround` может получить последнюю выполненную `Surround` функцию
- lastValue: представляет собой возвращаемое значение `lastOrigin`
- isGetter: `Логическое значение` является ли текущий метод геттером
- isSetter: `Логическое значение` является ли текущий метод сеттером
- annotate: `Объект` текущий объект аннотации
- trans: `Объект` представляет собой кэшированный переменный текущего транзакта, изменения этого переменного позволяют следующим выполняющимся методам `after` или `onError` получать изменённое значение
- Возвращаемое значение: заменяет возвращаемое значение исходного метода, если есть ещё одна аннотация `@Surround`, то `lastValue` будет передана следующему `Surround`
- Не обязательный
- `onError`
- Тип: `Функция`
- По умолчанию: пустая функция
- Описание: вызывается при возникновении ошибки во время выполнения данного метода (включая `before` и `after`) - Коллбэк параметры: (объектная форма) - params: Исходные параметры вызова метода - `Объект - {ключ:значение}`
- annotations: Список аннотаций, применённых к данному методу - `Массив<Объект>`
- propertyEntity: Энтити свойства, содержащий информацию о имени свойства и прочее
- lastOrigin: В случае наличия нескольких аннотаций `@Surround`, внешний `Surround` может получить последнюю вызванную аннотацию `Surround` - `Объект`
- isGetter: `boolean` Указывает, является ли текущий метод getter
- isSetter: `boolean` Указывает, является ли текущий метод setter
- annotate: `Объект` Текущий объект аннотации
- trans: `Объект` Объект кэшированного состояния текущего метода, изменения которого могут быть использованы в методах `after` или `onError` для получения изменённых значений
- error: Объект ошибки
- resolve: Метод для решения ошибки, вызов этого метода указывает на то, что ошибка не будет отображаться, аргумент представляет собой нормальное значение возврата - `Функция(result)`
- возвращаемое значение: Если параметр resolve пуст, используется это возвращаемое значение
- необязательный параметр
- `isAsync` `Удалён`#### `@Annotate`
Эта аннотация используется для создания новых аннотаций и должна применяться к классу. Название нового аннотирования совпадает с названием класса. Пример использования:
```javascript
// Аннотируем класс, который мы хотим создать как новый тип аннотации
@Annotate
class MyAnnotate {
/**
* Параметры аннотации
*/
a = 10;
b = 100;
}
// Используем созданную аннотацию
@MyAnnotate({a: 50})
class TestClass {}
С помощью приведённого выше кода мы определяем аннотацию @Annotate
и используем её для декорирования класса TestClass
, при этом параметр a
установлен равным 50
.
Примечание: Аннотации, созданные с помощью данной аннотации, пока не поддерживают наследование
Действует на уровне: класс
По умолчанию: нет
Параметры аннотации:
extends
*Describe
или конкретной аннотации
Yöntem: BasicAnnotationDescribe
@DefaultParam
Должна использоваться вместе с @Annotate
. Может применяться только к членским переменным. Если переменная декорируется этой аннотацией, она становится значением по умолчанию для параметра в новой аннотации.
Пример:
@Annotate
class MyAnnotate {
/**
* Параметры аннотации
*/
a = 10;
@DefaultParam
b = 100;
}
// Значение параметра b по умолчанию равно 50
@MyAnnotate(b=50)
class TestClass {}
```Примечание: **Если несколько параметров одной аннотации декорированы, применяется только первый**
#### `@DynamicParam` Должен использоваться вместе с `@Annotate`, действует только на члены типа `функция`. Если метод декорирован этой аннотацией, это означает, что этот параметр является динамическим методом, то есть значение этого параметра будет равно возвращаемому значению метода. Этот метод принимает следующие параметры (в виде объекта):
- `classEntity`
- Представляет объект класса, который будет декорирован данной аннотацией
- Эффективен только при применении аннотации к классу
- `propertyEntity`
- Представляет объект члена класса, который будет декорирован данной аннотацией
- Эффективен только при применении аннотации к члену класса
Пример:
```javascript
@Annotate
class MyAnnotate {
/**
* Параметры
*/
a = 10;
@DynamicParam
b({classEntity}) {
return classEntity.name;
}
}
// Значение параметра b будет установлено как 'TestClass'
@MyAnnotate
class TestClass {}
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )