spring boot访问mongodb方法2

前面已经通过spring boot完成了对mongodb的集群访问, 但是访问方法中应用的配置是固定, 详情参考: spring boot访问mongodb副本集方法1

这里我们将完善前面的程序, 变为一个使用的, 可用通过控制台或者参数文件进行设置的可用系统

一. spring boot中传参的方法

 1、自动化配置

spring Boot 对于开发人员最大的好处在于可以对 Spring 应用进行自动配置。Spring Boot 会根据应用中声明的第三方依赖来自动配置 Spring 框架,而不需要进行显式的声明。比如
当声明了对 HSQLDB 的依赖时,Spring Boot 会自动配置成使用 HSQLDB 进行数据库操作。

Spring Boot 推荐采用基于 Java 注解的配置方式,而不是传统的 XML。只需要在主配置 Java 类上添加“@EnableAutoConfiguration”注解就可以启用自动配置。Spring Boot 的自动配置
功能是没有侵入性的,只是作为一种基本的默认实现。开发人员可以通过定义其他 bean 来替代自动配置所提供的功能。比如当应用中定义了自己的数据源 bean 时,自动配置所提供的
HSQLDB 就不会生效。这给予了开发人员很大的灵活性。既可以快速的创建一个可以立即运行的原型应用,又可以不断的修改和调整以适应应用开发在不同阶段的需要。可能在应用最开始的时
候,嵌入式的内存数据库(如 HSQLDB)就足够了,在后期则需要换成 MySQL 等数据库。Spring Boot 使得这样的切换变得很简单。

2、外部化的配置

在应用中管理配置并不是一个容易的任务,尤其是在应用需要部署到多个环境中时。通常会需要为每个环境提供一个对应的属性文件,用来配置各自的数据库连接信息、服务器信息和第
三方服务账号等。通常的应用部署会包含开发、测试和生产等若干个环境。不同的环境之间的配置存在覆盖关系。测试环境中的配置会覆盖开发环境,而生产环境中的配置会覆盖测试环境。
Spring 框架本身提供了多种的方式来管理配置属性文件。Spring 3.1 之前可以使用 PropertyPlaceholderConfigurer。Spring 3.1 引入了新的环境(Environment)和概要信息(Profile)
API,是一种更加灵活的处理不同环境和配置文件的方式。不过 Spring 这些配置管理方式的问题在于选择太多,让开发人员无所适从。Spring Boot 提供了一种统一的方式来管理应用的配置
,允许开发人员使用属性文件、YAML 文件、环境变量和命令行参数来定义优先级不同的配置值。

Spring Boot 所提供的配置优先级顺序比较复杂。按照优先级从高到低的顺序,具体的列表如下所示。

(1) 命令行参数
(2) java:comp/env 里的JNDI属性
(3) JVM系统属性
(4) 操作系统环境变量
(5) 随机生成的带 random.* 前缀的属性(在设置其他属性时,可以引用它们,比如 ${random.
long} )
(6) 应用程序以外的application.properties或者appliaction.yml文件
(7) 打包在应用程序内的application.properties或者appliaction.yml文件
(8) 通过 @PropertySource 标注的属性源
(9) 默认属性

这个列表按照优先级排序,也就是说,任何在高优先级属性源里设置的属性都会覆盖低优先级的相同属性。例如,命令行参数会覆盖其他属性源里的属性。

application.properties和application.yml文件能放在以下四个位置。
(1) 外置,在相对于应用程序运行目录的/config子目录里。
(2) 外置,在应用程序运行的目录里。
(3) 内置,在config包内。
(4) 内置,在Classpath根目录。
同样,这个列表按照优先级排序。也就是说,/config子目录里的application.properties会覆盖应用程序Classpath里的application.properties中的相同属性。
此外,如果我们在同一优先级位置同时有application.properties和application.yml,那么application.yml里的属性会覆盖application.properties里的属性。
命令行参数

通过Java -jar app.jar --name="Spring" --server.port=9090方式来传递参数。

SpringApplication 类默认会把以“--”开头的命令行参数转化成应用中可以使用的配置参数,如 “--name=Alex” 会设置配置参数 “name” 的值为 “Alex”.

可以使用的参数可以是我们自己定义的,也可以是Spring Boot中默认的参数。

注意:命令行参数在app.jar的后面!

可以通过SpringApplication.setAddCommandLineProperties(false)禁用命令行配置。
Java系统属性

注意Java系统属性位置java -Dname="isea533" -jar app.jar,可以配置的属性都是一样的,优先级不同。

例如java -Dname="isea533" -jar app.jar --name="Spring!"中name值为Spring.

有些系统,关于一些数据库或其他第三方账户等信息,由于安全问题,其配置并不会提前配置在项目中暴露给开发人员。
对于这种情况,我们在运行程序的时候,可以通过参数指定一个外部配置文件。
以 demo.jar 为例,方法如下: java -jar demo.jar --spring.config.location=/opt/config/application.properties

其中文件名随便定义,无固定要求。

RandomValuePropertySource

RandomValuePropertySource 可以用来生成测试所需要的各种不同类型的随机值,从而免去了在代码中生成的麻烦。RandomValuePropertySource 可以生成数字和字符串。数字的类型
包含 int 和 long,可以限定数字的大小范围。以“random.”作为前缀的配置属性名称由 RandomValuePropertySource 来生成:

系统中用到随机数的地方,例如 使用 RandomValuePropertySource 生成的配置属性:

user.id=${random.value}
user.count=${random.int}
user.max=${random.long}
user.number=${random.int(100)}
user.range=${random.int[100, 1000]

random.int*支持value参数和,max参数,当提供max参数的时候,value就是最小值

3. 读取属性

读取application.yml中的localhost配置的值

package com.gwc.context;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
* Created by gwcheng on 2017/3/6.
*/
@Component
@ConfigurationProperties
public class SystemProperties
{
private String localhost;

public String getLocalhost() {
return localhost;
}

public void setLocalhost(String localhost) {
this.localhost = localhost;
}
}

还可以在Bean中使用

@Value("${server.port}")
private String serverPort;

来读取配置文件中的属性值

当前目录的“/config”子目录。
当前目录。
classpath 中的“/config”包。
classpath

上面的顺序也表示了该位置上包含的属性文件的优先级。优先级按照从高到低的顺序排列。 即:/config优先于classpath根目录

可以通过“spring.config.name”配置属性来指定不同的属性文件名称。也可以通过“spring.config.location”来添加额外的属性文件的搜索路径。如果应用中包含多个 profile,
可以为每个 profile 定义各自的属性文件,按照“application-{profile}”来命名。

@PropertySource优先级比较

这个注解可以指定具体的属性配置文件,优先级比较低。

SpringApplication.setDefaultProperties

例如:

SpringApplication application = new SpringApplication(Application.class);
Map<String, Object> defaultMap = new HashMap<String, Object>();
defaultMap.put("name", "Isea-Blog");
//还可以是Properties对象
application.setDefaultProperties(defaultMap);
application.run(args);
应用程序使用属性@Value(“${xxx}”)

对于配置属性,可以在代码中通过“@Value”来使用,如:

@RestController
@EnableAutoConfiguration
public class Application {
@Value("${name}")
private String name;
@RequestMapping("/")
String home() {
return String.format("Hello %s!", name);
}
}

变量 name 的值来自配置属性中的“name”属性。

比如application.properties有 port=8081

@Value("${port:8082}")
private String port;

即可获取8081这个值

属性占位符

例如:

app.name=MyApp

app.description=${app.name} is a Spring Boot application

可以在配置文件中引用前面配置过的属性(优先级前面配置过的这里都能用)。

通过如${app.name:默认名称}方法还可以设置默认值,当找不到引用的属性时,会使用默认的属性。

由于${}方式会被Maven处理。如果你pom继承的spring-boot-starter-parent,Spring Boot 已经将maven-resources-plugins默认的${}方式改为了@ @方式,例如@name@。

如果你是引入的Spring Boot,你可以修改使用其他的分隔符

通过属性占位符还能缩短命令参数

例如修改web默认端口需要使用--server.port=9090方式,如果在配置中写上:

server.port=${port:8080}

那么就可以使用更短的--port=9090,当不提供该参数的时候使用默认值8080。

属性名匹配规则

例如有如下配置对象:

@Component
@ConfigurationProperties(prefix="person")
public class ConnectionSettings {
private String firstName;
}

firstName可以使用的属性名如下:

person.firstName,标准的驼峰式命名
person.first-name,虚线(-)分割方式,推荐在.properties和.yml配置文件中使用
PERSON_FIRST_NAME,大写下划线形式,建议在系统环境变量中使用

属性验证

可以使用JSR-303注解进行验证,例如:

@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionSettings {

@NotNull
private InetAddress remoteAddress;

// ... getters and setters

}

这些内容已经足够本文使用来做一个可以配置的连接mongodb的程序了, 网络上还有更多的介绍关于spring boot属性文件的, 大家可以去百度搜搜。

二. 改造 spring boot的mongodb项目

1. 改造中遇到的一个问题

经过上面的关于spring boot的摸索, 编写了如下一个代码

package cn.iigrowing.study.mongodb;

... 代码详情后面会提供

@Component    // 这个很重要, 否则类不会被扫描
public class MyMongoClient {
 @Bean   // 生命这个是一个bean
 @ConditionalOnMissingBean(MongoClient.class)   // 说明这个替换那个默认的
 public MongoClient getMongodbClients() {
 
 ..... 
 
 MongoCredential credential = MongoCredential.createMongoCRCredential(myUsername, database, myPassword.toCharArray());
 MongoClient client = new MongoClient(addresses, Arrays.asList(credential));
 return client;
 }
 
 // mongodb://192.168.128.189:27017,192.168.128.132:27017,192.168.128.133:27017/mytest03
 @Value("${mongodb.uri}")    // 获取uri然后进行解析
 private String myUri;
 
 @Value("${mongodb.username}")  // 获取用户名
 private String myUsername;
 
 @Value("${mongodb.password}")  // 获取密码
 private String myPassword;

}

然后启动程序, 最后报如下异常:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
 at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:779)
 at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:760)
 at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:747)
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1162)
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1151)
 at cn.iigrowing.study.mongodb.Application.main(Application.java:14)
Caused by: org.springframework.data.mongodb.UncategorizedMongoDbException: Command failed with error 13: 'not authorized on test to execute command { insert: "customer", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: "cn.iigrowing.study.mongodb.Customer", firstName: "Alice", lastName: "Smith", address: { aa: "100000", ddd: "20000" } } ] }' on server node3.iigrowing.cn:27017. The full response is { "ok" : 0.0, "errmsg" : "not authorized on test to execute command { insert: \"customer\", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: \"cn.iigrowing.study.mongodb.Customer\", firstName: \"Alice\", lastName: \"Smith\", address: { aa: \"100000\", ddd: \"20000\" } } ] }", "code" : 13 }; nested exception is com.mongodb.MongoCommandException: Command failed with error 13: 'not authorized on test to execute command { insert: "customer", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: "cn.iigrowing.study.mongodb.Customer", firstName: "Alice", lastName: "Smith", address: { aa: "100000", ddd: "20000" } } ] }' on server node3.iigrowing.cn:27017. The full response is { "ok" : 0.0, "errmsg" : "not authorized on test to execute command { insert: \"customer\", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: \"cn.iigrowing.study.mongodb.Customer\", firstName: \"Alice\", lastName: \"Smith\", address: { aa: \"100000\", ddd: \"20000\" } } ] }", "code" : 13 }
 at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:107)
 at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:2135)
 at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:481)
 at org.springframework.data.mongodb.core.MongoTemplate.insertDBObject(MongoTemplate.java:1046)
 at org.springframework.data.mongodb.core.MongoTemplate.doInsert(MongoTemplate.java:855)
 at org.springframework.data.mongodb.core.MongoTemplate.insert(MongoTemplate.java:796)
 at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:80)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
 at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
 at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
 at com.sun.proxy.$Proxy45.save(Unknown Source)
 at cn.iigrowing.study.mongodb.Application.run(Application.java:28)
 at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:776)
 ... 6 common frames omitted
Caused by: com.mongodb.MongoCommandException: Command failed with error 13: 'not authorized on test to execute command { insert: "customer", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: "cn.iigrowing.study.mongodb.Customer", firstName: "Alice", lastName: "Smith", address: { aa: "100000", ddd: "20000" } } ] }' on server node3.iigrowing.cn:27017. The full response is { "ok" : 0.0, "errmsg" : "not authorized on test to execute command { insert: \"customer\", ordered: true, documents: [ { _id: ObjectId('5955fad2bb844c23906eff87'), _class: \"cn.iigrowing.study.mongodb.Customer\", firstName: \"Alice\", lastName: \"Smith\", address: { aa: \"100000\", ddd: \"20000\" } } ] }", "code" : 13 }
 at com.mongodb.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:115)
 at com.mongodb.connection.WriteCommandProtocol.receiveMessage(WriteCommandProtocol.java:268)

为了查明原因仔细检查了前文:   spring boot访问mongodb副本集方法1

里面的访问方法, 并且运行了那个程序, 程序正确执行, 说明用户名和密码等都正确, 那到底是哪里的问题?

最后发现两个项目的默认配置文件不同, 如下图

spring-boot中不同配置名称造成启动问题
spring-boot中不同配置名称造成启动问题

原因分析, 在spring boot中 ,程序会自动扫描配置, 根据这些配置属性, 来启动一系列的相关程序, 在刚刚有问题程序中, 由于没有采用默认的spring的mongodb的配置名称, 可能造成spring boot的mongodb环境不完整,因此有问题, 然后修改配置问题后, 启动程序, 程序正确运行了。

另外程序运行过程中有大量日志输出, 不利观察程序运行情况, 对日志进行了设置, 请参考: Spring Boot日志管理

然后在application.properties 文件中, 添加 logging.level.root=INFO

修改了默认的日志级别

2. 修正后的改造

修改应用的 application.properties 配置文件如下

spring.data.mongodb.username=mytest03
spring.data.mongodb.password=password
spring.data.mongodb.uri=mongodb://192.168.128.189:27017,192.168.128.132:27017,192.168.128.133:27017/mytest03

logging.level.root=INFO

以及读取的配置文件的部分

@Value("${spring.data.mongodb.uri}")
 private String myUri;
 
 @Value("${spring.data.mongodb.username}")
 private String myUsername;
 
 @Value("${spring.data.mongodb.password}")
 private String myPassword;

然后从新编译了程序, 启动程序后运行正确

程序源代码 请下载    iigrowing.mongo02

 

3. 测试各种修改参数的方法

拷贝jar文件到一个临时目录中

创建一个config目录, 在目录里面创建配置文件application.properties

内容下

spring.data.mongodb.username=mytest03
spring.data.mongodb.password=password
spring.data.mongodb.uri=mongodb://192.168.128.133:27017,192.168.128.132:27017,192.168.128.189:27017/mytest03

logging.level.root=INFO

然后用  java -jar iigrowing.mongo02-0.0.1.jar

最后对比两次输出的结果如下图

spring-boot不同目录下配置的优先级
spring-boot不同目录下配置的优先级

通过上图我们应该能了解到, 文章前面说的 配置文件的优先级了,

其他的优先级还有很多,大家可以自己去测试了。

发表评论