Grails配置
1. 启动类配置
Grails应用程序启动类Application.groovy
位于grails-app/init
目录。
Application.groovy
类继承了启动 Grails 应用程序配置的基类grails.boot.config.GrailsAutoConfiguration
,并有静态void Main方法,这意味着它可以作为常规应用程序启动。
默认的Application.groovy
启动类代码
package helloworldgrails4
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import groovy.transform.CompileStatic
@CompileStatic
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
}
看看他继承的grails.boot.config.GrailsAutoConfiguration
类有哪些配置
package grails.boot.config
import grails.config.Config
import grails.core.GrailsApplication
import grails.boot.config.tools.ClassPathScanner
import grails.core.GrailsApplicationClass
import groovy.transform.CompileStatic
import org.grails.compiler.injection.AbstractGrailsArtefactTransformer
import org.grails.spring.aop.autoproxy.GroovyAwareAspectJAwareAdvisorAutoProxyCreator
import org.springframework.aop.config.AopConfigUtils
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
import org.springframework.context.annotation.Bean
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import java.lang.reflect.Field
/**
* A base class for configurations that bootstrap a Grails application
* 引导 Grails 应用程序的配置的基类
*
* @since 3.0
* @author Graeme Rocher
*
*/
@CompileStatic
// WARNING: Never add logging to the source of this class, early initialization causes problems
// 警告:永远不要在这个类的源代码中添加日志,过早的初始化会导致问题
class GrailsAutoConfiguration implements GrailsApplicationClass, ApplicationContextAware {
private static final String APC_PRIORITY_LIST_FIELD = "APC_PRIORITY_LIST"
static {
try {
// patch AopConfigUtils if possible
// 如果可能,修补 AopConfigUtils
Field field = AopConfigUtils.class.getDeclaredField(APC_PRIORITY_LIST_FIELD)
if(field != null) {
field.setAccessible(true)
Object obj = field.get(null)
List<Class<?>> list = (List<Class<?>>) obj
list.add(GroovyAwareAspectJAwareAdvisorAutoProxyCreator.class)
}
} catch (Throwable e) {
// ignore
}
}
ApplicationContext applicationContext
/**
* @return A post processor that uses the {@link grails.plugins.GrailsPluginManager} to configure the {@link org.springframework.context.ApplicationContext}
* 使用grails.plugins.GrailsPluginManager配置ApplicationContext后处理器
*/
@Bean
GrailsApplicationPostProcessor grailsApplicationPostProcessor() {
return new GrailsApplicationPostProcessor( this, applicationContext, classes() as Class[])
}
/**
* @return The classes that constitute the Grails application
* 构成 Grails 应用程序的类
*/
Collection<Class> classes() {
Collection<Class> classes = new HashSet()
ClassPathScanner scanner = new ClassPathScanner()
if(limitScanningToApplication()) {
classes.addAll scanner.scan(getClass(), packageNames())
}
else {
classes.addAll scanner.scan(new PathMatchingResourcePatternResolver(applicationContext), packageNames())
}
ClassLoader classLoader = getClass().getClassLoader()
for(cls in AbstractGrailsArtefactTransformer.transformedClassNames) {
try {
classes << classLoader.loadClass(cls)
} catch (ClassNotFoundException cnfe) {
// ignore
}
}
return classes
}
/**
* Whether classpath scanning should be limited to the application and not dependent JAR files. Users can override this method to enable more broad scanning
* at the cost of startup time.
* 类路径扫描是否应仅限于应用程序而不是依赖的 JAR 文件。 用户可以覆盖此方法,以启动时间为代价启用更广泛的扫描。
*
* @return True if scanning should be limited to the application and should not include dependant JAR files
* 返回:如果扫描应仅限于应用程序且不应包括相关的 JAR 文件,则为 True
*/
protected boolean limitScanningToApplication() {
return true
}
/**
* @return The packages to scan
* 返回:要扫描的包
*/
Collection<Package> packages() {
def thisPackage = getClass().package
thisPackage ? [ thisPackage ] : new ArrayList<Package>()
}
/**
* @return The package names to scan. Delegates to {@link #packages()} by default
* 返回:要扫描的包名称。 默认情况下委托给packages()
*/
Collection<String> packageNames() {
packages().collect { Package p -> p.name }
}
/**
* 返回:一个定义要被 Spring 注册的 bean 的闭包
*/
@Override
Closure doWithSpring() { null }
/**
* 在org.springframework.context.ApplicationContext在插件可以添加动态方法的阶段刷新后调用。 子类应该覆
*/
@Override
void doWithDynamicMethods() {
// no-op
}
/**
* 一旦org.springframework.context.ApplicationContext被刷新并且在 {#doWithDynamicMethods()} 被调用后调用
*/
@Override
void doWithApplicationContext() {
// no-op
}
/**
* 当应用程序配置更改时调用
* 参数:事件
*/
@Override
void onConfigChange(Map<String, Object> event) {
// no-op
}
/**
* 调用一次所有先前的初始化钩子: doWithSpring() 、 doWithDynamicMethods()和doWithApplicationContext()
*/
@Override
void onStartup(Map<String, Object> event) {
// no-op
}
/**
* 当org.springframework.context.ApplicationContext关闭时调用
*/
@Override
void onShutdown(Map<String, Object> event) {
// no-op
}
GrailsApplication getGrailsApplication() {
applicationContext.getBean(GrailsApplication)
}
Config getConfig() {
grailsApplication.config
}
}
Application.groovy启动类中可以做哪些事情
1. 启动Grails应用程序
2. 自定义包扫描路径
默认情况下,Grails将扫描控制器,领域类等的所有已知源目录,但是如果希望扫描其他JAR文件中的包,则可以通过覆盖应用程序类的packageNames()
方法来实现:
class Application extends GrailsAutoConfiguration {
@Override
Collection<String> packageNames() {
super.packageNames() + ['my.additional.package']
}
...
}
3. 注册bean
应用程序类也可以用作注册Spring Bean对象,只需定义有bean注解的方法,返回的对象作为bean对象,方法的名称用作bean名称。
class Application extends GrailsAutoConfiguration {
@Bean
MyType myBean() {
return new MyType()
}
...
}
4. 应用程序生命周期管理
Application.groovy
类继承了启动 Grails 应用程序配置的基类grails.boot.config.GrailsAutoConfiguration
,而grails.boot.config.GrailsAutoConfiguration
又实现了grails.core.GrailsApplicationLifeCycle
接口。这个接口提供了Grails应用程序的生命周期钩子方法,所有插件都实现了这个接口,我们可以通过重写这些钩子方法来扩展一些功能。
class Application extends GrailsAutoConfiguration {
/**
* 返回:一个定义要被 Spring 注册的 bean 的闭包
*/
@Override
Closure doWithSpring() {
{->
// 要注册的 bean
mySpringBean(MyType)
}
}
...
}
2. 环境配置
Grails支持多环境配置。 grails-app / conf目录中的application.yml可以使用yaml语法进行多环境配置。
2.1 默认的多环境配置
例如application.yml默认的多环境配置:
# 配置数据库链接通用属性
dataSource:
pooled: true
jmxExport: true
driverClassName: org.h2.Driver
username: sa
password: ''
environments:
# 开发环境数据源配置,在上面通用数据源配置上进行扩展
development:
dataSource:
# 开发环境使用create-drop,服务每次启动时创建数据库和表,服务器停止时删除数据库和表
dbCreate: create-drop
# 模式使用H2内存数据库,也可以配置成文件数据库,H2数据库默认用户名sa,密码空。还可以在conf/application.yml中开启控制台界面
url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
# 测试环境数据源配置,在上面通用数据源配置上进行扩展
test:
dataSource:
# 测试环境使用update,每次启动会更新表结构,服务停止不会删除数据库和表
dbCreate: update
# 模式使用H2内存数据库
url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
# 生产环境数据源配置,在上面通用数据源配置上进行扩展
production:
dataSource:
# 生产环境配置为none
dbCreate: none
# 模式使用H2文件数据库
url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
# 配置数据源的其他信息
properties:
jmxEnabled: true
initialSize: 5
...
注意上面使用了dataSource提供了公共配置信息,然后在每个环境配置中又配置了dataSource,这样可以使用公共的配置,然后再添加各自的特殊环境配置。
2.2 预设的环境
Grails预设了3个环境:
dev:开发环境
prod:生产环境
test:测试环境
2.3 打包和运行使用不同的环境
使用测试环境打包
grails test war
使用grails.env
环境变量配置环境
grails -Dgrails.env=test run-app
2.4 在代码启动过程中判断当前环境
grails-app/init/BootStrap.groovy
文件可以在项目启动时做一些初始化配置,针对不同的环境设置不同的配置。
例如:
def init = { ServletContext ctx ->
environments {
production {
// 开发环境设置一个env的属性信息
ctx.setAttribute("env", "prod")
}
development {
// 开发环境启动项目时初始化一条book记录
new Book(title: "《Grails教程》").save()
}
}
// 任何环境下启动项目,都设置一个作者年龄的属性配置信息
ctx.setAttribute("book.author.age", 18)
}
2.5 在代码中运行过程中判断当前环境
在代码中使用 Environment 类检测环境。
import grails.util.Environment
...
switch (Environment.current) {
// 当前是开发环境
case Environment.DEVELOPMENT:
// 做一些开发环境的特殊处理
configureForDevelopment()
break
// 当前是生产环境
case Environment.PRODUCTION:
// 做一些生成环境的特殊处理
configureForProduction()
break
}
3. 数据源配置
在上面的conf/application.yml
多环境配置中,我们已经看到了数据源相关的配置,一般情况下都是这两者相互结合使用的。
Grails使用H2作为默认数据库,如果要使用其他数据库,需要添加对应的JDBC驱动程序,因为Grails是基于java构建的。
配置Mysql数据源
1. 添加mysql驱动程序依赖项
Grails使用Gradle构建项目,需要在build.gradle的dependencies中添加
runtime 'mysql:mysql-connector-java:5.1.39'
根据自己使用的mysql版本,选择对应的驱动包版本。
2. 修改application.yml中的数据库连接信息
这里只修改开发环境中的数据库连接信息,其他环境不做修改。
# 配置数据库链接通用属性
dataSource:
pooled: true
jmxExport: true
driverClassName: org.h2.Driver
username: sa
password: ''
environments:
# 开发环境数据源配置,在上面通用数据源配置上进行扩展
development:
dataSource:
# 开发环境使用create-drop,服务每次启动时创建数据库和表,服务器停止时删除数据库和表
dbCreate: create-drop
# 使用 mysql 驱动
driverClassName: com.mysql.jdbc.Driver
# 使用 mysql 数据库连接,加上 useUnicode 和 characterEncoding 参数防止中文乱码
url: jdbc:mysql://localhost:3306/grails4_hello?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
test:
dataSource:
...
3. 创建数据库grails4_hello
CREATE DATABASE grails4_hello
;
4. 启动项目测试数据库连接
因为数据库配置使用dbCreate: create-drop
启动项目后,查看数据库中已经自动创建了book表。
打开浏览器访问http://localhost:8080/book/create
添加一条书籍记录
添加后查看数据库中,已经成功插入一条记录
到这里Grails已经成功连接到mysql数据库了,这里只添加了一些简单的配置像,还有其他更多的配置项也比较重要。
5. 配置参数说明
grails-app/conf/application.yml
中可以做下面这些配置:
配置项 | 说明 |
---|---|
driverClassName | JDBC 驱动程序的类名。例如Mysql:driverClassName: com.mysql.jdbc.Driver |
username | JDBC 连接的用户名 |
password | JDBC 连接的密码 |
url | 数据库的 JDBC 连接 |
dbCreate | 是否从域模型自动生成数据库 - ‘create-drop’、‘create’、‘update’、‘validate’ 或 ‘none’ 之一 |
pooled | 是否使用连接池(默认为 true) |
logSql | 启用 SQL 日志记录到标准输出 |
formatSql | 格式化记录的 SQL |
dialect | 方言 - 表示用于与数据库通信的Hibernate方言的字符串或类。有关可用方言,请参阅org.hibernate.dialect包。 |
readOnly | 如果true使数据源只读,这会导致连接池在每个连接上调用setReadOnly(True) |
transactional | 事务性 - 如果false将DataSource的TransactionManage Bean留出在链接的BE1PC事务管理器实现之外。这仅适用于其他数据源。 |
persistenceInterceptor | 默认数据源是自动连接到持久性拦截器,除非这设置为true,否则其他数据源不会自动连接,用于多数据源。 |
properties | 在DataSource Bean上设置的额外属性。请参阅Tomcat池文档。还有一个javadoc格式的属性文件。 |
jmxExport | 如果为false,则将禁用所有数据源的JMX MBean的注册。默认情况下,JMX MBeans为jmxEnabled = True的数据源添加了数据库。 |
type | 连接池类型,如果要强制Grails在有多个可用时使用它。 |
dbCreate参数说明
Hibernate可以自动创建领域类所需的数据库表。可以通过dbCreate属性来控制何时以及如何创建这些表。
参数配置说明:
create
:删除现有架构并在启动时创建架构,删除现有表,索引等。create-drop
:与创建相同,但也会在应用程序退出时删除表。update
:创建缺少的表和索引,不删除任何表或数据。注意,这不能处理表或者属性的更改,例如领域类属性重命名,会添加新列,但不会删除旧列和数据。validate
:对数据库没有更改。将配置与现有数据库架构进行比较并报告警告。none
:什么都不做
当应用程序的架构相对稳定,建议将dbCreate设置设置为“none”。
下面是Mysql 金典配置示例,使用的是groovy脚本配置,重点关注参数的配置,只需要将{
改成:
号就变成yml配置了:
dataSource {
pooled = true
dbCreate = "update"
url = "jdbc:mysql://localhost:3306/my_database"
driverClassName = "com.mysql.jdbc.Driver"
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
username = "username"
password = "password"
type = "com.zaxxer.hikari.HikariDataSource"
properties {
jmxEnabled = true
initialSize = 5
maxActive = 50
minIdle = 5
maxIdle = 25
maxWait = 10000
maxAge = 10 * 60000
timeBetweenEvictionRunsMillis = 5000
minEvictableIdleTimeMillis = 60000
validationQuery = "SELECT 1"
validationQueryTimeout = 3
validationInterval = 15000
testOnBorrow = true
testWhileIdle = true
testOnReturn = false
jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED
}
}
使用额外属性的高级配置示例
dataSource {
pooled = true
dbCreate = "update"
url = "jdbc:mysql://localhost:3306/my_database"
driverClassName = "com.mysql.jdbc.Driver"
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
username = "username"
password = "password"
type = "com.zaxxer.hikari.HikariDataSource"
properties {
// Tomcat JDBC Pool 文档
// http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Common_Attributes
// https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/tomcat/jdbc/pool/PoolConfiguration.html
jmxEnabled = true
initialSize = 5
maxActive = 50
minIdle = 5
maxIdle = 25
maxWait = 10000
maxAge = 10 * 60000
timeBetweenEvictionRunsMillis = 5000
minEvictableIdleTimeMillis = 60000
validationQuery = "SELECT 1"
validationQueryTimeout = 3
validationInterval = 15000
testOnBorrow = true
testWhileIdle = true
testOnReturn = false
ignoreExceptionOnPreLoad = true
// http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#JDBC_interceptors
jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED // safe default
// controls for leaked connections
abandonWhenPercentageFull = 100 // settings are active only when pool is full
removeAbandonedTimeout = 120
removeAbandoned = true
// use JMX console to change this setting at runtime
logAbandoned = false // causes stacktrace recording overhead, use only for debugging
// JDBC driver properties
// Mysql as example
dbProperties {
// Mysql specific driver properties
// http://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html
// let Tomcat JDBC Pool handle reconnecting
autoReconnect=false
// truncation behaviour
jdbcCompliantTruncation=false
// mysql 0-date conversion
zeroDateTimeBehavior='convertToNull'
// Tomcat JDBC Pool's StatementCache is used instead, so disable mysql driver's cache
cachePrepStmts=false
cacheCallableStmts=false
// Tomcat JDBC Pool's StatementFinalizer keeps track
dontTrackOpenResources=true
// performance optimization: reduce number of SQLExceptions thrown in mysql driver code
holdResultsOpenOverStatementClose=true
// enable MySQL query cache - using server prep stmts will disable query caching
useServerPrepStmts=false
// metadata caching
cacheServerConfiguration=true
cacheResultSetMetadata=true
metadataCacheSize=100
// timeouts for TCP/IP
connectTimeout=15000
socketTimeout=120000
// timer tuning (disable)
maintainTimeStats=false
enableQueryTimeouts=false
// misc tuning
noDatetimeStringSync=true
}
}
}
6. 默认了开启数据库控制台
Grails默认使用H2数据库,也开启了H2数据库控制台,可以使用web界面连接数据库,管理数据库,特别是针对内存数据库非常方便。
浏览器输入http://localhost:8080/h2-console
访问web界面数据库控制台。
登录成功后,我们可以执行sql命令
可以在grails-app/conf/application.yml
中配置spring.h2.console.enabled=false
来关闭H2控制台。
7. 是否需要手动关闭H2数据库控制台?
H2数据库控制台开启需要3个前提条件:
- 应用程序是基于
Servlet
的Web应用 - classpath中包含
com.h2database:h2
- 使用了
spring-boot-devtools
插件
查看build.gradle
的dependencies
,发现使用了spring-boot-devtools依赖:
developmentOnly("org.springframework.boot:spring-boot-devtools")
这个依赖表示只在开发环境中添加依赖,当项目打包时不包含这个插件,就不能满足上面的3个条件了,所以打包后的生产环境无法访问http://localhost:8088/h2-console
,不需要手动关闭H2数据库控制台。
更多H2配置参考Spring Boot H2控制台文档
多数据源配置
默认情况下,所有领域类使用一个数据源和单个数据库。
也可以将领域类数据库保存在两个或多个数据源中。
一般情况下使用不到,暂不做讲解。
4. 小结
本章主要介绍了以下知识点:
- 在哪里注册bean。
- 在代码中如何判断当前项目的运行环境(开发环境、生产环境、测试环境)。
- 如何配置多环境数据源。
- 如何连接Mysql数据库。
- 如何使用H2数据库控制台管理数据库。