SpringBoot 开发随笔

Spring Boot 配置

邮件发送

在本地开发环境测试,Spring Boot 能够正常发送邮件,但部署到阿里云 ECS 服务器以后,一直没有收到邮件,部分关键日志信息如下:

1
2
3
4
5
6
7
8
org.springframework.mail.MailSendException: Mail server connection failed; nested exception is com.sun.mail.util.MailConnectException: Couldn't connect to host, port: smtp.163.com, 25; timeout -1;
nested exception is:
java.net.ConnectException: 连接超时 (Connection timed out). Failed messages: com.sun.mail.util.MailConnectException: Couldn't connect to host, port: smtp.163.com, 25; timeout -1;
nested exception is:
java.net.ConnectException: 连接超时 (Connection timed out)
at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:447)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:322)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:311)

从现有情况看,跟程序运行环境有关,查看相关资料,发现在阿里云 ECS 服务器上,默认禁用了 25 端口,所以在通过 25 端口去连接邮件服务器时,无法连上,就报超时了。官方建议使用 465 端口,而 456 端口是 SSL 协议的,所以不仅要换端口,还需要进行 SSL 协议替换。下面是在 application.properties 进行的邮件发送相关配置,经过这样配置后,在阿里 ECS 上就能够正常发送邮件了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Mail Config
spring.mail.host=smtp.163.com
spring.mail.username=xxx@163.com
spring.mail.password=xxxxx
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true

# SSL Config
spring.mail.port=465
spring.mail.protocol=smtp
spring.mail.default-encoding=UTF-8
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.socketFactory.port=465
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory

163 邮箱相关服务器信息如下:

spring-boot-mail

bootstrap.yml 与 application.yml 的区别

加载顺序

  • bootstrap.yml > application.yml > application-dev.yml
  • bootstrap.yml 作用于应用程序上下文的引导阶段,bootstrap.yml 由父 Spring ApplicationContext 加载
  • bootstrap.ymlapplication.yml 在同一目录下时,bootstrap.yml 先加载,application.yml 后加载
  • application.propertiesapplication.yml 在同一目录下时,且存在相同的配置,则 application.properties 会覆盖 application.yml 里面的属性,因为 application.properties 会后加载,也就是说哪个文件被最后加载,哪个才具有最高级

配置区别

  • bootstrap.ymlapplication.yml 都可以用来配置参数
  • bootstrap.yml 用来程序引导时执行,应用于更加早期配置信息读取,可以理解成系统级别的一些参数配置,这些参数一般是不会变动的,一旦 bootStrap.yml 被加载,则内容不会被覆盖
  • application.yml 用来定义应用级别的配置参数,即应用程序特有的配置信息,可以用来配置后续各个模块中需使用的公共参数等。如果加载的 application.yml 的内容标签与 bootstrap.yml 的标签一致,那么 application.yml 会覆盖 bootstrap.yml, 而 application.yml 里面的内容可以动态替换

典型应用场景

  • 一些加密 / 解密的场景
  • 一些固定的不能被覆盖的属性
  • 当使用 Spring Cloud Config Server 的时候,应该在 bootstrap.yml 里面指定 spring.application.namespring.cloud.config.server.git.uri。这是因为当使用 Spring Cloud 的时候,配置信息一般是从 Config Server 加载的,为了取得配置信息(比如密码等),需要一些提早的或引导配置。因此,把 Config Server 信息放在 bootstrap.yml,用来加载真正需要的配置信息。

扫描父模块的 Mapper 类与 XML 映射文件

子模块若希望扫描到父模块里 MyBatis 的 Mapper 类与 XML 映射文件,需要加入以下配置:

  • 在主启动类上指定 Mapper 扫描的包名,父模块的包名命名规则必须符合 com.shop.**.mapper 规则
1
2
3
4
5
@MapperScan(basePackages = "com.shop.**.mapper")
@SpringBootApplication(scanBasePackages = "com.shop")
public class AuthApplication {

}
  • 在 MyBatis-Plus 的配置中,指定 XML 映射文件的扫描路径,同时指定 MyBatis 的类型别名扫描规则
1
2
3
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
type-aliases-package: com.shop.**.entity

Spring Boot 单元测试

基础使用

引入 Maven 依赖

1
2
3
4
5
6
7
8
9
10
11
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.18.RELEASE</version>
</parent>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

添加 @RunWith@SpringBootTest 注解

1
2
3
4
5
6
7
8
9
@RunWith(SpringRunner.class)
@SpringBootTest
public class SimpleTest {
@Test
public void doTest() {
int num = new Integer(1);
Assert.assertEquals(num, 1);
}
}

其中有两个 runner 可以选择,分别是 SpringRunnerSpringJUnit4ClassRunner。如果是在 Junit 4.3 之前,只能选择 SpringJUnit4ClassRunner,如果是 Junit 4.3 之后,建议选择 SpringRunner,其中 SpringRunner 仅仅继承了 SpringJUnit4ClassRunner,没有任何的额外代码。

1
2
3
4
5
6
7
8
9
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class SimpleTest {
@Test
public void doTest() {
int num = new Integer(1);
Assert.assertEquals(num, 1);
}
}

注解说明

  • @RunWith:标识为 JUnit 的运行环境
  • @SpringBootTest:获取启动类、加载配置,确定装载 Spring Boot
  • @Test:声明需要测试的方法
  • @BeforeClass:针对所有测试,只执行一次,且必须被 static void 修饰
  • @AfterClass:针对所有测试,只执行一次,且必须被 static void 修饰
  • @Before:每个测试方法运行前都会执行的方法
  • @After:每个测试方法运行后都会执行的方法
  • @Ignore:忽略方法

断言测试

断言测试也就是期望值测试,是单元测试的核心,也就是决定测试结果的表达式,Assert 对象中的断言方法如下:

  • Assert.assertEquals:对比两个值相等
  • Assert.assertNotEquals:对比两个值不相等
  • Assert.assertSame:对比两个对象的引用相等
  • Assert.assertArrayEquals:对比两个数组相等
  • Assert.assertTrue:验证返回是否为真
  • Assert.assertFlase:验证返回是否为假
  • Assert.assertNull:验证 Null
  • Assert.assertNotNull:验证非 Null

超时测试

@Test 注解设置 timeout 属性即可,时间单位为毫秒:

1
@Test(timeout = 1000)

数据库测试

在测试数据操作的时候,若不想让测试数据污染数据库,只需要给测试类或者测试方法添加 @Transactional 注解即可,这样既可以测试数据操作方法,又不会污染数据库,即默认会回滚对数据库的所有写操作。

1
2
3
4
5
6
7
8
9
10
11
@Test
@Transactional
public void saveTest() {
User user = new User();
user.setName("Adam");
user.setAge(19);
user.setPwd("123456");
userRepository.save(user);
System.out.println("userId:" + user.getId());
Assert.assertTrue(user.getId()>0);
}

Web 模拟测试

在 Spring Boot 项目里面可以直接使用 Junit 对 Web 项目进行测试,Spring 提供了 TestRestTemplate 对象,使用这个对象可以很方便的进行请求模拟。

Web 测试只需要进行两步操作:

  • @SpringBootTest 注解上设置 webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,即使用随机端口
  • 使用 TestRestTemplate 类进行 POST 或 GET 请求
1
2
3
4
5
6
7
8
9
10
11
12
13
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerTest {

@Autowired
private TestRestTemplate restTemplate;

@Test
public void getName() {
String name = restTemplate.getForObject("/name", String.class);
Assert.assertEquals("Adam", name);
}
}

其中 getForObject() 的含义代表执行 GET 请求,并返回 Object 类型的结果,第二个参数表示将返回结果转换为 String 类型,更多的请求方法如下:

  • getForEntity:Get 请求,返回实体对象(可以是集合)
  • postForEntity:Post 请求,返回实体对象(可以是集合)
  • postForObject:Post 请求,返回对象