@molay/spring4js
v0.1.0-alpha-1
Published
The javascript version implementation of spring mvc.
Downloads
4
Maintainers
Readme
Spring4js
The javascript version implementation of spring mvc.
Install
NOTICE: This library needs legacy decorator support!
yarn add @molay/spring4js
yarn add -D @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators babel-plugin-parameter-decorator
.babelrc
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"babel-plugin-parameter-decorator",
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
]
]
}
Usage
Dependency Injection
import { SPRING } from '@molay/spring4js';
const { Container, Decorators } = SPRING;
const { Component, Autowired, Qualifier, Configuration, Bean, Lazy } = Decorators;
class Person {
name;
hp;
}
class Weapon {
name;
damage;
attack(person) {
person.hp -= this.damage;
}
}
@Component()
class Gun extends Weapon {
name = 'gun';
damage = 1000;
}
@Lazy()
@Component({ name: 'rifle' })
class Rifle extends Weapon {
name = 'rifle';
damage = 2000;
}
@Component()
class Bomb extends Weapon {
name = 'bomb';
damage = 3000;
constructor() {
super();
console.log('[Bomb created!]');
}
}
@Component()
class Alice extends Person {
name = 'Alice';
hp = 1000;
@Autowired({ type: Gun })
weapon;
}
@Component()
class Bob extends Person {
name = 'Bob';
hp = 3000;
@Autowired({ type: Bomb })
weapon;
}
@Lazy()
@Component({ name: 'eve' })
class Eve extends Person {
name = 'Eve';
hp = 2000;
weapon;
@Autowired({ type: Weapon })
@Qualifier({ name: 'rifle' })
setWeapon(weapon) {
this.weapon = weapon;
}
}
@Lazy()
@Component()
class Scene {
@Autowired({ type: Alice })
alice;
@Autowired({ type: Bob })
bob;
@Autowired({ type: Person })
@Qualifier({ name: 'eve' })
eve;
@Autowired({ type: Object, required: false })
@Qualifier({ name: 'not exists' })
objectNotExists;
play() {
const { alice, bob, eve } = this;
const persons = [alice, bob, eve];
const numRounds = 10;
for (let i = 0; i < numRounds && persons.length > 0; i++) {
const person0 = persons[Math.floor(Math.random() * persons.length)];
const person1 = persons[Math.floor(Math.random() * persons.length)];
const message = [];
message.push(`${person0.name} (HP ${person0.hp}) uses ${person0.weapon.name} (DAMAGE ${person0.weapon.damage}) to`);
if (person0 !== person1) {
message.push(`attack ${person1.name} (HP ${person1.hp}).`);
}
else {
message.push(`suicide.`);
}
person0.weapon.attack(person1);
if (person1.hp <= 0) {
message.push(`${person1.name} has died.`);
persons.splice(persons.indexOf(person1), 1);
}
else message.push(`${person1.name} has ${person1.hp} points hp remaining.`);
console.log(`Round ${i + 1}: ${message.join(' ')}`);
}
}
}
const container = new Container().init();
const bean = container.getBean(Scene);
bean.play();
Spring MVC
import { SPRING, RequestMethod } from '@molay/spring4js';
const { Container, Decorators } = SPRING;
const { Controller, RequestMapping, AroundInvoke, Autowired, Configuration, Bean } = Decorators;
import Koa from 'koa';
// Around controller method and do sth
const CustomResponseProcess = function () {
return AroundInvoke({
method: function (bean, methodKey, args) {
const a = Date.now();
const returnValue = bean[methodKey].apply(bean, args);
const t = Date.now() - a;
const responseTime = `${t}ms`;
const [ctx] = args;
ctx.set('Content-Type', 'application/json');
ctx.set('X-Response-Time', responseTime);
return returnValue;
}
});
};
// Simulate bean object that needs to be asynchronously initialized
class ComplexBean {
async _init01() {
return new Promise(
(value) => {
const timeout = 100 + Math.random() * 900;
setTimeout(() => {
console.log(`[${new Date().toISOString()}] ComplexBean._init01 done after ${timeout.toFixed(3)}ms.`);
value();
}, timeout);
},
(reason) => {
}
);
}
async _init02() {
return new Promise(
(value) => {
const timeout = 100 + Math.random() * 900;
setTimeout(() => {
console.log(`[${new Date().toISOString()}] ComplexBean._init02 done after ${timeout.toFixed(3)}ms.`);
value();
}, timeout);
},
(reason) => {
}
);
}
async init() {
await this._init01();
await this._init02();
}
}
@Controller()
class Controller01 {
@Autowired({ type: ComplexBean })
complexBean;
@CustomResponseProcess()
@RequestMapping({ path: '/hello', method: RequestMethod.ALL })
async hello(ctx) {
ctx.status = 200;
ctx.body = 'Hello World!';
}
@CustomResponseProcess()
@RequestMapping({ path: '/echo', method: [RequestMethod.GET, RequestMethod.POST] })
async echo(ctx) {
ctx.status = 200;
ctx.body = JSON.stringify(ctx);
}
@CustomResponseProcess()
@RequestMapping({ path: '/test/:a?/:b?/:c?' })
async test(ctx, @Autowired({ type: ComplexBean }) complexBean) {
ctx.status = 200;
ctx.body = JSON.stringify({
flag: complexBean === this.complexBean,
...ctx.pathParams
});
}
}
@Controller()
@RequestMapping({ path: '/user' })
class Controller02 {
@CustomResponseProcess()
@RequestMapping({ path: '/', method: RequestMethod.POST })
async create(ctx) {
ctx.status = 200;
ctx.body = JSON.stringify({
status: 200,
message: 'Create user succeed.'
});
}
@CustomResponseProcess()
@RequestMapping({ path: '/:id', method: RequestMethod.GET })
async read(ctx) {
ctx.status = 200;
ctx.body = JSON.stringify({
status: 200,
data: {
}
});
}
@CustomResponseProcess()
@RequestMapping({ path: '/:id', method: RequestMethod.PUT })
async update(ctx) {
ctx.status = 200;
ctx.body = JSON.stringify({
status: 200,
message: 'Update user succeed.'
});
}
@CustomResponseProcess()
@RequestMapping({ path: '/:id', method: RequestMethod.DELETE })
async delete(ctx) {
ctx.status = 200;
ctx.body = JSON.stringify({
status: 200,
message: 'Delete user succeed.'
});
}
}
// Custom configuration
@Configuration()
class Config {
@Bean({ type: ComplexBean })
getComplexBean(@Autowired({ type: Container }) container) {
const bean = container.createBean(ComplexBean);
bean.init();
return bean;
}
}
const app = new Koa();
app.listen(3000);
new Container()
.setWebContext(app)
.init();
Decorators
@Autowired
- type: Function; Declares the annotated dependency type.
- required?: boolean = true; Declares whether the annotated dependency is required.
@Component({ name: 'a' })
class ClassA {
}
@Component({ name: 'b' })
class ClassB {
}
class ClassC {
@Autowired({ type: ClassA })
objectA;
// can not found matched bean, but no exception.
@Autowired({ type: ClassB, require: false })
@Qualifier({ name: 'b2' })
objectB;
// inject bean to parameter when method invoking
// can not found matched bean, throw exception
doSth(
@Autowired({ type: ClassB })
@Qualifier({ name: 'b2' })
objectB2
) {
}
}
@Qualifier
- name?: string; Declares the name for looking up the annotated dependency.
@Configuration
class BeanA {
}
@Component()
class ComponentA {
@Autowired({ type: BeanA })
beanA;
}
@Configuration()
class Config {
@Bean()
getBeanA() {
const bean = new BeanA();
// do sth
// ...
return bean;
}
}
@Bean
@Lazy
@Value
@Component
- name?: string; Declares the bean name.
@Controller
- name?: string; Declares the bean name.
@Service
- name?: string; Declares the bean name.
@Repository
- name?: string; Declares the bean name.
@RequestMapping
- path: string | string[]; Declares the path mapping URIs.
- method?: string | string[] = RequestMethod.ALL; Declares the HTTP request methods to map to.
- [ ] params?: any; TODO
- [ ] headers?: any; TODO
- [ ] consumes?: any; TODO
- [ ] produces?: any; TODO
@Component()
class UserDao {
}
@Controller()
@RequestMapping({ path: '/user' })
class UserController {
@RequestMapping({ path: '/', method: RequestMethod.POST })
async create(ctx, @Autowired({ type: UserDao }) dao) {
// do sth
// dao.create(...)
}
@RequestMapping({ path: '/:id', method: RequestMethod.GET })
async read(ctx) {
}
@Autowired({ type: UserDao })
dao;
@RequestMapping({ path: '/:id', method: RequestMethod.PUT })
async update(ctx) {
// do sth
// this.dao.update(...)
}
@RequestMapping({ path: '/:id', method: RequestMethod.DELETE })
async delete(ctx) {
}
}
@BeforeInvoke
- method: (bean: any, methodKey: string | symbol, args: any[]) => void; The method will be called before the target method is called.
@AfterInvoke
- method: (bean: any, methodKey: string | symbol, args: any[], returnValue: any) => void; The method will be called after the target method is called.
@AroundInvoke
- method: (bean: any, methodKey: string | symbol, args: any[]) => any; The method will be called when the target method is called, you can customize the control logic of the target method. Need to pay attention to the return value.