1. 介绍
是基础组件,提供GORM的底层支持、项目框架的基础功能、 OpenSessionInViewInterceptor的拦截器、系统错误整体处理、代码生成器的控制器等功能
2. 使用
使用在线initilizer工具时,会随着组件的选择自动添加 gradle中
implementation('org.yunchen.gb:gb-core:1.4.0.0.M1')
3. 功能描述
规范项目框架结构如下(参见规约中解释):
TIP:
src
groovy
app
conf
controller
domain
init
job
service
Application.groovy
ServletInitializer.groovy
resource
i18n
static
templates
thymeleaf3
tools
scaffolding
test
build.gradle
4. 默认开发规约
4.1. 默认参数:系统会在request中提供key为isAjax的Attribute,值为boolean类型,用于controller中判断当前是否为ajax访问.
4.2. 分页处理:系统默认的分页支持类是PageParams,支持前端bootstrap Table或easyui datagrid的分页访问
属性 | description | 类型 | 默认值 |
---|---|---|---|
max |
每页的条数 |
int |
10 |
limit |
每页的条数(非必须项 和max参数二选一即可) |
int |
10 |
offset |
当前数据的起始位置 |
int |
0 |
sort |
排序字段 |
String |
id |
order |
排序顺序 |
String |
desc |
4.3. controller的参数组装
系统扩展spring MVC的参数组装功能,提供基于domain类的自动组装,遵循如下原则:
提交表单参数中若没有id参数,则系统自动创建全新的domain对象,并将其余参数自动赋值。 如果提交表单参数中包含id参数,则系统会调用domain类的get(id)方法,获取domain类的数据库实例,并将其余参数自动赋值。 赋值过程中自动忽略version、clob、blob、byte[]类型的字段赋值。如是Date或Time类型的字段,会调用domain类上字段的@DateTimeFormat注解,来实现自动日期赋值。 如果提交表单参数中包含外键的参数,使用 referenceDomain.id的模式,如“baseUser.id”,赋值时,系统会自动调用findById(id)方法获取外键对象实例,赋值为domain对象。
详细参见工程中用户、角色、登录记录等默认实现
默认生成代码的映射结构
action name | view name | 描述 |
---|---|---|
index |
index.html |
列表首页 |
json |
无 |
返回表格json数据 |
create |
create.html |
创建页面 |
save |
无 |
保存处理返回json数据 |
edit |
edit.html |
修改页面 |
update |
无 |
修改处理返回json数据 |
show |
show.html |
展示页面 |
detele |
无 |
单条删除处理返回json数据 |
deteles |
无 |
多条删除处理返回json数据 |
download |
无 |
下载excel字节流 |
4.4. json 输出
系统默认使用spring MVC内置的jacksonJSON进行json转换输出。
4.4.1. 在domain类上使用@JsonIgnoreProperties进行属性过滤,将GORM中的一些属性排除出json的氛围,如下:
@JsonIgnoreProperties(["errors", "metaClass", "dirty", "attached", "dirtyPropertyNames","handler","target","session","entityPersisters","hibernateLazyInitializer","initialized","proxyKey","children"]) @Entity class SystemLoginRecord implements GormEntity<SystemLoginRecord> { 。。。。。 }
4.4.2. 使用@JsonFormat注解指明Date类型字段转换为json的规则,如下:
@JsonFormat(pattern = "yyyy-MM-dd",timezone="GMT+8") Date loginTime
4.4.3. 使用@JsonSerialize(using=GbDomainSimpleJsonSerializer.class)注解来指明domain类的外键对象json规则,默认生成id,label,class三个属性(序列id、显示label,class类名)
@JsonSerialize(using=GbDomainSimpleJsonSerializer.class) BaseUser baseUser
4.4.4. 使用@GbDomainSimpleJsonFormat注解配合JsonSerialize来定制化domain类的外键对象json规则,支持values和ignores两种字段设置方式
@JsonSerialize(using=GbDomainSimpleJsonSerializer.class) @GbDomainSimpleJsonFormat(ignores=['version','dateCreated','lastUpdated']) BaseUser baseUser
4.5. 内置乐观锁
系统使用GORM进行数据的对象关系映射ORMAPPING,因此默认会为每一个domain类提供id、version两个内置属性。 id默认是long型的自增主键.可以通过mapping闭包设置为sequence或UUID version字段是GORM内部维护的乐观锁,当数据发生修改时,version会自动增加1,系统使用它来判断是否发生了数据脏读,避免脏写。
5. 提供辅助类
5.1. annotation 注解
5.1.1. GbController 注解
用于提供controller类的自动RequestMapping映射,从而使的系统开发人员不必再手工设置RequestMapping和指定view视图的名称。
5.1.2. GbRestController 注解
增加GbRestController注解,读取application.yml中的配置 gb.rest.prefix 为controller的requestmap增加前缀
gb:
rest:
prefix: #/api
默认为空,不影响系统运行 |
5.1.3. GbInterceptor 注解
用于提供拦截器的注解,系统扫描添加此注解的对象注册为拦截器。其中的value为拦截器的PathPatterns列表,而excludes是忽略的PathPatterns列表。
5.1.4. Title 注解
是系统为domain类的属性提供的国际化注解,其方法名与i8n目录下的属性文件名称一致,如zh_CN方法对应messages_zh_CN.properties资源文件。代码生成工具会读取属性的注解值来设置页面展示和i8n的属性配置值。
5.1.5. GbDomainSimpleJsonFormat注解
是针对jacksonJson转换对象为json时使用的注解,配合JsonSerialize来定制化domain类的外键对象json规则,支持values和ignores两种字段设置方式
@JsonSerialize(using=GbDomainSimpleJsonSerializer.class) @GbDomainSimpleJsonFormat(ignores=['version','dateCreated','lastUpdated']) BaseUser baseUser
5.2. 辅助服务类
5.2.1. GbSpringUtils类
GbSpringUtils类静态方法
action name | 描述 |
---|---|
getApplicationContext() |
获取 应用context |
getResource(String resource) |
获取资源 |
getBean(String name) |
获取bean |
isDomain(String domainName) |
是否domain类 |
getDomain(String domainName) |
获取domain类 |
getDomainConstraintsMap(Class domainClass) |
获取domain的约束定义 |
getConfiginfo(String key) |
获取application.yml的配置信息 |
getI18nMessage(String code,List arguments,String defaultMessage,Locale locale) |
获取i18n资源的信息 |
getI18nMessage(String code,List arguments,String defaultMessage) |
获取i18n资源的信息 |
getI18nMessage(String code,List arguments) |
获取i18n资源的信息 |
getI18nMessage(String code) |
获取i18n资源的信息 |
publishEvent(Object event) |
发布事件 |
publishEvent(AppEvent event) |
发布系统事件 |
addApplicationListener(ApplicationListener<?> listener) |
添加事件监听(订阅事件) |
5.2.2. PageParams类
支持前端bootstrap Table或easyui datagrid的分页访问
属性 | description | 类型 | 默认值 |
---|---|---|---|
max |
每页的条数 |
int |
10 |
limit |
每页的条数(非必须项 和max参数二选一即可) |
int |
10 |
offset |
当前数据的起始位置 |
int |
0 |
sort |
排序字段 |
String |
id |
order |
排序顺序 |
String |
desc |
5.2.3. 关于分页类的强制限制
PageParam类有一个强制限制,max的值不能大于100,这在页面展示中没有问题,但当在服务端其他场景下复用此类时就比较麻烦,需要 绕开此限制。 PageParam类有一个的构造函数,接受boolean值的参数,可以关闭max⇐100的强制限制,因为页面访问 时,由controller委托spring 构建PageParam参数,因此不受改动影响,任然执行强制限制 示例如下:
PageParam pageParam = new PageParam(false);
List allList=baseUserService.list(pageParams,{});
5.2.4. GORM
关于GORM的动态方法和使用方式,参阅GORM增强方法内的相关内容
6. 系统部署
6.1. 默认支持spring boot的标准模式部署, 因此只需要打包运行即可。
运行开发工具的gradle的build或buildDependents,查看工程的build/libs目录,可以看到生成的jar文件。
在生产环境中运行命令java -jar 命令。
java -jar demo.jar
6.2. 容器部署(tomcat)
Spring Boot内嵌容器支持Tomcat、Jetty、Undertow、jetty.
6.2.1. tomcat
tomcat 8 之后无须进行xml配置,使用gradle刷新类库依赖后,使用gradle buildDependents打war包后,部署即可。
tomcat部署的优化设置
修改conf/server.xml,在contenxt的Connector中增加URIEncoding="UTF-8"
增加java options: -Xms4096m –Xmx4096m //建议配置内存数值及以上 -XX:PermSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=256m
7. rest接口支持
因为使用angularJs,vuejs,react等客户端方案时,提交至服务器端的请求的content-type,可能为application/x-www-form-urlcoded,application/json,application/xml
而普通的form方式,提交至服务器端的请求的content-type,可能为application/x-www-form-urlcoded,multipart/form-data
TIP:springMVC推荐使用RequestBody注解,但经测发现此注解只支持controller方法中的一个参数,赋值为提交的json或xml整体string字符串
提供如下兼容的方式处理
=== application.yml配置
[source,yml] ---- gb: mvc: autoTransJson2parameter: true # true or false (1) parameterTypeDefault: newInstance # null or newInstance (2) ----
1 | 是否需要提供request reader 到request parameter的转换 |
2 | 默认没有对应的参数,是提供默认实例还是null值 |
3 | 不支持RequestBody注解,请删除controller中的RequestBody批注 |
可以配合GbRestController注解一起使用 |
7.1. post提交数据的读取
因为使用contentType为application/json模式发送至服务器端的数据,只能从request.reader中读取一次。 因此提供了数据缓存,以便多次读取。使用方式如下,取出的map就是由发送的json数据转换成的对象。 或是 List 类型的json数据。 如果转换失败,会将Object和错误信息放到request的属性中。
//Map Map requestJsonMap=(Map) request.getAttribute(GbSpringUtils.GB_REQUEST_JSON_MAP) //List List requestJsonList=(List) request.getAttribute(GbSpringUtils.GB_REQUEST_JSON_LIST) //Object Object requestJsonObject=(Object) request.getAttribute(GbSpringUtils.GB_REQUEST_JSON_OBJECT) String errInfo=(String) request.getAttribute(GbSpringUtils.GB_REQUEST_JSON_ERROR_INFO)
8. 关于json的服务端操作类
框架中默认集成jackson json.
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core'
为避免重复发明轮子, 框架并未将jackson json的操作包装类文档化公开, 也建议直接使用jackson json的底层类进行json操作
以下示例json的读取和生成
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper objectMapper = new ObjectMapper();
//将对象转换为json 字符串
String jsonString=objectMapper.writeValueAsString(object);
//将json 字符串转换为对象
Map jsonMap=objectMapper.readValue(jsonString?:"{}",Map.class);
转换后的map对象,groovy语法上支持逐级级联调用,非常方便。如: jsonMap.user.username |
也可使用groovy内置的JsonSlurper来操作json
def map = new JsonSlurper().parseText('{"id":1,"name":"Thinking in Java"}')
println map.id
println map.name
9. 关于操作xml
使用MarkupBuilder生成xml和XmlSlurper解析xml
//MarkupBuilder
def mb = new MarkupBuilder(new File('book.xml').newPrintWriter())
mb.book() {
author('Lao Zhang')
title('Groovy')
publisher('中国邮电出版社')
isbn("123456")
}
//XmlSlurper
String text="""
<book>
<author>Lao Zhang</author>
<title>Groovy</title>
<publisher>中国邮电出版社</publisher>
<isbn parent="parment">123456</isbn>
</book>
"""
def root = new XmlSlurper().parse(text)
println(root.isbn.@parent)
println(root.author)
10. 关于Service注解的注意事项
因为框架中集成了GORM,因此默认会有grails.gorm.services.Service注解,与org.springframework.stereotype.Service注解会产生混淆
需要开发者牢记,我们标注service类时,要注意使用org.springframework.stereotype.Service注解.
11. linux环境及docker的urandom问题
/dev/random和/dev/urandom是Linux系统中提供的随机伪设备,这两个设备的任务,是提供永不为空的随机字节数据流。很多解密程序与安全应用程序(如SSH Keys,SSL Keys等)需要它们提供的随机数据流。
这两个设备的差异在于:/dev/random的random pool依赖于系统中断,因此在系统的中断数不足时,/dev/random设备会一直封锁,尝试读取的进程就会进入等待状态,直到系统的中断数充分够用, /dev/random设备可以保证数据的随机性。/dev/urandom不依赖系统的中断,也就不会造成进程忙等待,但是数据的随机性也不高。
war 包模式运行会碰到这类问题影响性能,建议增加-Djava.security.egd=file:/dev/./urandom参数避免之.
示例如下:
#!/bin/sh
java -Djava.security.egd=file:/dev/./urandom -jar /app/application.war
也可以通过在docker中部署解压后的应用程序,绕开此问题 |
12. 关于多模块或多项目下的domain类共享的解决方案
在domain类的父目录增加一个Config.groovy类,增加@Configuration注解,如下:
@Configuration
@EnableAutoConfiguration
class DomainAutoConfig {
}
13. 事件机制
核心默认提供事件AppStartupEvent、AppShutdown和事件基类AppEvent,编写相关的listener可订阅相关事件.
若订阅基类AppEvent事件,则能收到全部框架发布的事件。 |
13.1. 订阅事件
13.1.1. 使用独立listener类订阅
编写listener类来订阅事件
@Configuration
@Slf4j
class NewAppListener implements ApplicationListener<AppStartupEvent> {
@Override
void onApplicationEvent(AppStartupEvent event) {
println "i receiver system startup event: ${event}";
}
}
13.1.2. 简便方法订阅
也可使用GbSpringUtils辅助类的静态方法订阅
GbSpringUtils.addApplicationListener(new ApplicationListener<AppEvent>() {
@Override
void onApplicationEvent(AppEvent event) {
println "i receiver one system event: ${event}"
}
})
13.2. 发布事件
使用GbSpringUtils辅助类的静态方法可以发布事件
GbSpringUtils.publishEvent(new AppEvent('测试事件'));
14. 切换web容器
14.1. 使用Undertow替换tomcat
14.1.1. 修改build.gradle文件
dependencies { compile("org.springframework.boot:spring-boot-starter-undertow:${springBootVersion}") (1) compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}") { exclude module: "spring-boot-starter-tomcat" (2) exclude module: "tomcat-embed-core" exclude module: "spring-boot-starter-logging" exclude module: "logback-classic" } ...... ...... ...... testCompile("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") { exclude module: "spring-boot-starter-tomcat" (2) exclude module: "tomcat-embed-core" } testCompile("org.springframework.boot:spring-boot-test-autoconfigure:${springBootVersion}") { exclude module: "spring-boot-starter-tomcat" (2) exclude module: "tomcat-embed-core" } ...... ...... ...... }
1 | 添加undertow的依赖 |
2 | 去除tomcat的依赖 |
14.1.2. 修改application类
去除对tomcat的类引用
1 | 注释tomcatFactory的bean |
14.1.3. 配置undertow
可在application.yml中增加对undertow的配置,以下是一些示例
server.undertow.io-threads: 16 server.undertow.worker-threads: 256 server.undertow.buffer-size: 1024 server.undertow.buffers-per-region: 1024 server.undertow.direct-buffers: true
14.1.4. 查看效果
运行application类,从输出日志中可以看到Undertow已替代tomca作为web容器启动
2020-04-12 10:53:21.937 INFO 14392 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler' 2020-04-12 10:53:22.079 INFO 14392 --- [ main] io.undertow : starting server: Undertow - 2.0.27.Final 2020-04-12 10:53:22.095 INFO 14392 --- [ main] org.xnio : XNIO version 3.3.8.Final 2020-04-12 10:53:22.113 INFO 14392 --- [ main] org.xnio.nio : XNIO NIO Implementation Version 3.3.8.Final 2020-04-12 10:53:22.217 INFO 14392 --- [ main] o.s.b.w.e.u.UndertowServletWebServer : Undertow started on port(s) 8080 (http) with context path '/' 2020-04-12 10:53:22.221 INFO 14392 --- [ main] c.c.c.e.w.e.EnterpriseApplication : Started EnterpriseApplication in 15.776 seconds (JVM running for 18.086)
==