Browse Source

分布式id号段获取

master
tangmingyou 4 years ago
parent
commit
14899b291e
  1. 4
      im-common/src/main/java/net/sopod/soim/common/util/ImClock.java
  2. 7
      im-das/im-das-user/pom.xml
  3. 6
      im-das/im-das-user/src/main/java/net/sopod/soim/das/user/service/HelloServiceImpl.java
  4. 2
      im-das/im-das-user/src/main/resources/application.yml
  5. 4
      im-das/im-das-user/src/test/java/nets/sopod/soim/das/user/dao/DasImUserShardingTest.java
  6. 35
      im-das/im-das-user/src/test/java/nets/sopod/soim/das/user/service/SegmentIdGeneratorTest.java
  7. 2
      im-logic-api/im-logic-user-api/src/main/java/net/sopod/soim/logic/user/auth/model/ReqLogin.java
  8. 4
      im-logic-api/im-logic-user-api/src/main/java/net/sopod/soim/logic/user/auth/service/AuthService.java
  9. 2
      im-logic-api/im-logic-user-api/src/main/java/net/sopod/soim/logic/user/service/UserService.java
  10. 17
      im-logic-api/im-segment-id-api/pom.xml
  11. 37
      im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/config/SegmentIdAutoConfiguration.java
  12. 34
      im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/core/Segment.java
  13. 56
      im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/core/SegmentIdGenerator.java
  14. 2
      im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/model/NextSegmentParam.java
  15. 4
      im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/model/SegmentRange.java
  16. 17
      im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/service/SegmentIdService.java
  17. 13
      im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/segmentid/api/model/Segment.java
  18. 18
      im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/segmentid/api/service/SegmentIdService.java
  19. 1
      im-logic-api/im-segment-id-api/src/main/resources/META-INF/spring.factories
  20. 66
      im-logic/im-logic-user/pom.xml
  21. 3
      im-logic/im-logic-user/src/main/java/net/sopod/soim/logic/user/LogicUserApplication.java
  22. 10
      im-logic/im-logic-user/src/main/java/net/sopod/soim/logic/user/service/UserServiceImpl.java
  23. 2
      im-logic/im-segment-id/pom.xml
  24. 17
      im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/service/SegmentIdServiceImpl.java
  25. 6
      im-logic/im-segment-id/src/test/java/net/sopod/soim/logic/segmentid/test/SegmentIdApplicationTest.java
  26. 18
      pom.xml

4
im-common/src/main/java/net/sopod/soim/common/util/ImClock.java

@ -40,8 +40,10 @@ public class ImClock {
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}
public static void setClock(Clock newClock) {
@Deprecated
private static void setClock(Clock newClock) {
Preconditions.checkNotNull(newClock);
// 有循环超时判断依赖,回调导致一直循环
Preconditions.checkArgument(
clock.millis() <= newClock.millis(),
"不允许回调时间: current=%s, new=%s",

7
im-das/im-das-user/pom.xml

@ -12,6 +12,11 @@
<artifactId>im-das-user</artifactId>
<dependencies>
<dependency>
<groupId>net.sopod</groupId>
<artifactId>im-common</artifactId>
<version>${soim.version}</version>
</dependency>
<dependency>
<groupId>net.sopod</groupId>
<artifactId>im-das-user-api</artifactId>
@ -19,7 +24,7 @@
</dependency>
<dependency>
<groupId>net.sopod</groupId>
<artifactId>im-common</artifactId>
<artifactId>im-segment-id-api</artifactId>
<version>${soim.version}</version>
</dependency>
<dependency>

6
im-das/im-das-user/src/main/java/net/sopod/soim/das/user/service/HelloServiceImpl.java

@ -1,8 +1,11 @@
package net.sopod.soim.das.user.service;
import net.sopod.soim.logic.api.segmentid.core.SegmentIdGenerator;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.rpc.RpcContext;
import javax.annotation.Resource;
/**
* HelloServiceImpl
*
@ -12,6 +15,9 @@ import org.apache.dubbo.rpc.RpcContext;
@DubboService(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
@Resource
private SegmentIdGenerator segmentIdGenerator;
@Override
public String sayHello(String name) {
System.out.println("be invoked! client:" + RpcContext.getClientAttachment().getObjectAttachments());

2
im-das/im-das-user/src/main/resources/application.yml

@ -4,7 +4,7 @@ dubbo:
registry:
address: nacos://124.222.131.236:3848
protocol:
port: 8081
port: 3011
mybatis-plus:
mapper-locations: classpath:mapper/*.xml

4
im-das/im-das-user/src/test/java/nets/sopod/soim/das/user/test/DasUserApplicationTest.java → im-das/im-das-user/src/test/java/nets/sopod/soim/das/user/dao/DasImUserShardingTest.java

@ -1,4 +1,4 @@
package nets.sopod.soim.das.user.test;
package nets.sopod.soim.das.user.dao;
import net.sopod.soim.common.util.ImClock;
import net.sopod.soim.das.user.DasUserApplication;
@ -18,7 +18,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = DasUserApplication.class)
public class DasUserApplicationTest {
public class DasImUserShardingTest {
@Autowired
private UserMapper userMapper;

35
im-das/im-das-user/src/test/java/nets/sopod/soim/das/user/service/SegmentIdGeneratorTest.java

@ -0,0 +1,35 @@
package nets.sopod.soim.das.user.service;
import net.sopod.soim.common.util.ImClock;
import net.sopod.soim.das.user.DasUserApplication;
import net.sopod.soim.logic.api.segmentid.core.SegmentIdGenerator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
/**
* SegmentIdGeneratorTest
*
* @author tmy
* @date 2022-04-04 09:34
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = DasUserApplication.class)
public class SegmentIdGeneratorTest {
@Resource
private SegmentIdGenerator segmentIdGenerator;
@Test
public void test() {
long begin = ImClock.millis();
for (int i = 0; i < 100000; i++) {
System.out.println(segmentIdGenerator.nextId("im_user"));
}
System.out.println(ImClock.millis() - begin + "ms");
}
}

2
im-logic-api/im-logic-user-api/src/main/java/net/sopod/soim/logic/api/auth/model/ReqLogin.java → im-logic-api/im-logic-user-api/src/main/java/net/sopod/soim/logic/user/auth/model/ReqLogin.java

@ -1,4 +1,4 @@
package net.sopod.soim.logic.api.auth.model;
package net.sopod.soim.logic.user.auth.model;
/**
* LoginParam

4
im-logic-api/im-logic-user-api/src/main/java/net/sopod/soim/logic/api/auth/service/AuthService.java → im-logic-api/im-logic-user-api/src/main/java/net/sopod/soim/logic/user/auth/service/AuthService.java

@ -1,6 +1,6 @@
package net.sopod.soim.logic.api.auth.service;
package net.sopod.soim.logic.user.auth.service;
import net.sopod.soim.logic.api.auth.model.ReqLogin;
import net.sopod.soim.logic.user.auth.model.ReqLogin;
/**
* AuthService

2
im-logic-api/im-logic-user-api/src/main/java/net/sopod/soim/logic/api/user/service/UserService.java → im-logic-api/im-logic-user-api/src/main/java/net/sopod/soim/logic/user/service/UserService.java

@ -1,4 +1,4 @@
package net.sopod.soim.logic.api.user.service;
package net.sopod.soim.logic.user.service;
/**
* UserService

17
im-logic-api/im-segment-id-api/pom.xml

@ -12,5 +12,22 @@
<artifactId>im-segment-id-api</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

37
im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/config/SegmentIdAutoConfiguration.java

@ -0,0 +1,37 @@
package net.sopod.soim.logic.api.segmentid.config;
import net.sopod.soim.logic.api.segmentid.core.SegmentIdGenerator;
import net.sopod.soim.logic.api.segmentid.service.SegmentIdService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* SegmentIdAutoConfiguration
* o(o)
*
* @author tmy
* @date 2022-04-05 21:02
*/
@ConditionalOnProperty(
prefix = "im-segment-id",
name = {"enabled"},
matchIfMissing = true
)
@Configuration
public class SegmentIdAutoConfiguration {
@DubboReference
private SegmentIdService segmentIdService;
@Bean
public SegmentIdGenerator segmentIdGenerator() {
if (segmentIdService == null) {
throw new NullPointerException("segmentIdService bean not exists!");
}
return new SegmentIdGenerator(segmentIdService);
}
}

34
im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/core/Segment.java

@ -0,0 +1,34 @@
package net.sopod.soim.logic.api.segmentid.core;
import java.util.concurrent.atomic.AtomicLong;
/**
* Segment
*
* @author tmy
* @date 2022-04-03 23:12
*/
public class Segment {
private final AtomicLong atomicId;
private final long end;
public Segment(long begin, long end) {
atomicId = new AtomicLong(begin);
this.end = end;
}
public boolean hasNext() {
return this.atomicId.get() <= this.end;
}
public long next() {
long id = this.atomicId.getAndIncrement();
if (id > this.end) {
throw new IllegalStateException("id最大值超过范围");
}
return id;
}
}

56
im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/core/SegmentIdGenerator.java

@ -0,0 +1,56 @@
package net.sopod.soim.logic.api.segmentid.core;
import net.sopod.soim.logic.api.segmentid.model.SegmentRange;
import net.sopod.soim.logic.api.segmentid.service.SegmentIdService;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
/**
* SegmentGenerator
*
* @author tmy
* @date 2022-04-03 23:21
*/
public class SegmentIdGenerator {
private final SegmentIdService segmentIdService;
private final Map<String, LinkedList<Segment>> segmentsMap;
public SegmentIdGenerator(SegmentIdService segmentIdService) {
this.segmentIdService = segmentIdService;
segmentsMap = new HashMap<>();
}
/**
* TODO 动态增加步长
* TODO 剩余数一半时, 异步更新 segment
* @param bizTag 业务标签同一业务标签生成id唯一
* @return id
*/
public long nextId(String bizTag) {
if (bizTag == null || bizTag.isEmpty()) {
throw new IllegalStateException("bizTag can not be empty!");
}
synchronized (bizTag.intern()) {
LinkedList<Segment> segments = segmentsMap.computeIfAbsent(bizTag, tag -> {
LinkedList<Segment> nextSegments = new LinkedList<>();
SegmentRange range = segmentIdService.nextSegmentId(bizTag);
Segment segment = new Segment(range.getBeginId(), range.getEndId());
nextSegments.add(segment);
return nextSegments;
});
Segment segment = segments.getFirst();
if (!segment.hasNext()) {
SegmentRange nextRange = segmentIdService.nextSegmentId(bizTag);
segment = new Segment(nextRange.getBeginId(), nextRange.getEndId());
segments.removeFirst();
segments.add(segment);
}
return segment.next();
}
}
}

2
im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/segmentid/api/model/dto/NextSegmentParam.java → im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/model/NextSegmentParam.java

@ -1,4 +1,4 @@
package net.sopod.soim.logic.segmentid.api.model.dto;
package net.sopod.soim.logic.api.segmentid.model;
import lombok.Data;

4
im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/segmentid/api/model/dto/SegmentDTO.java → im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/model/SegmentRange.java

@ -1,4 +1,4 @@
package net.sopod.soim.logic.segmentid.api.model.dto;
package net.sopod.soim.logic.api.segmentid.model;
import lombok.Data;
import lombok.experimental.Accessors;
@ -13,7 +13,7 @@ import java.io.Serializable;
*/
@Data
@Accessors(chain = true)
public class SegmentDTO implements Serializable {
public class SegmentRange implements Serializable {
private static final long serialVersionUID = 4767453140231279111L;

17
im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/api/segmentid/service/SegmentIdService.java

@ -0,0 +1,17 @@
package net.sopod.soim.logic.api.segmentid.service;
import net.sopod.soim.logic.api.segmentid.model.SegmentRange;
/**
* SegmentIdService
*
* @author tmy
* @date 2022-04-02 23:07
*/
public interface SegmentIdService {
SegmentRange nextSegmentId(String bizTag);
SegmentRange nextSegmentId(String bizTag, Long step);
}

13
im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/segmentid/api/model/Segment.java

@ -1,13 +0,0 @@
package net.sopod.soim.logic.segmentid.api.model;
/**
* Segment
*
* @author tmy
* @date 2022-04-02 11:58
*/
public class Segment {
}

18
im-logic-api/im-segment-id-api/src/main/java/net/sopod/soim/logic/segmentid/api/service/SegmentIdService.java

@ -1,18 +0,0 @@
package net.sopod.soim.logic.segmentid.api.service;
import net.sopod.soim.logic.segmentid.api.model.dto.NextSegmentParam;
import net.sopod.soim.logic.segmentid.api.model.dto.SegmentDTO;
/**
* SegmentIdService
*
* @author tmy
* @date 2022-04-02 23:07
*/
public interface SegmentIdService {
SegmentDTO nextSegmentId(String bizTag);
SegmentDTO nextSegmentId(String bizTag, Long step);
}

1
im-logic-api/im-segment-id-api/src/main/resources/META-INF/spring.factories

@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=net.sopod.soim.logic.api.segmentid.config.SegmentIdAutoConfiguration

66
im-logic/im-logic-user/pom.xml

@ -11,5 +11,71 @@
<artifactId>im-logic-user</artifactId>
<dependencies>
<dependency>
<groupId>net.sopod</groupId>
<artifactId>im-logic-user-api</artifactId>
<version>${soim.version}</version>
</dependency>
<dependency>
<groupId>net.sopod</groupId>
<artifactId>im-common</artifactId>
<version>${soim.version}</version>
</dependency>
<dependency>
<groupId>net.sopod</groupId>
<artifactId>im-das-user-api</artifactId>
<version>${soim.version}</version>
</dependency>
<dependency>
<groupId>net.sopod</groupId>
<artifactId>im-segment-id-api</artifactId>
<version>${soim.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions><!-- 去掉springboot默认logback日志配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

3
im-logic/im-logic-user/src/main/java/net/sopod/soim/logic/user/UserMain.java → im-logic/im-logic-user/src/main/java/net/sopod/soim/logic/user/LogicUserApplication.java

@ -6,10 +6,11 @@ package net.sopod.soim.logic.user;
* @author tmy
* @date 2022-03-26 01:41
*/
public class UserMain {
public class LogicUserApplication {
public static void main(String[] args) {
}
}

10
im-logic/im-logic-user/src/main/java/net/sopod/soim/logic/user/service/UserServiceImpl.java

@ -0,0 +1,10 @@
package net.sopod.soim.logic.user.service;
/**
* UserServiceImpl
*
* @author tmy
* @date 2022-04-04 10:03
*/
public class UserServiceImpl {
}

2
im-logic/im-segment-id/pom.xml

@ -87,4 +87,6 @@
</exclusions>
</dependency>
</dependencies>
</project>

17
im-logic/im-segment-id/src/main/java/net/sopod/soim/logic/segmentid/service/SegmentIdServiceImpl.java

@ -6,8 +6,8 @@ import com.google.common.base.Preconditions;
import lombok.AllArgsConstructor;
import net.sopod.soim.common.util.ImClock;
import net.sopod.soim.common.util.ObjectUtil;
import net.sopod.soim.logic.segmentid.api.model.dto.SegmentDTO;
import net.sopod.soim.logic.segmentid.api.service.SegmentIdService;
import net.sopod.soim.logic.api.segmentid.model.SegmentRange;
import net.sopod.soim.logic.api.segmentid.service.SegmentIdService;
import net.sopod.soim.logic.segmentid.config.SegmentConfig;
import net.sopod.soim.logic.segmentid.mapper.SegmentIdMapper;
import net.sopod.soim.logic.segmentid.model.entity.SegmentId;
@ -38,12 +38,12 @@ public class SegmentIdServiceImpl implements SegmentIdService {
private final SegmentConfig segmentConfig;
@Override
public SegmentDTO nextSegmentId(String bizTag) {
public SegmentRange nextSegmentId(String bizTag) {
return nextSegmentId(bizTag, null);
}
@Override
public SegmentDTO nextSegmentId(String bizTag, Long step) {
public SegmentRange nextSegmentId(String bizTag, Long step) {
SegmentId segmentId = segmentIdMapper.selectById(bizTag);
if (segmentId == null) {
String key = SegmentConfig.KEY_PREFIX_SEGMENT_ID_INSERT + bizTag;
@ -79,12 +79,13 @@ public class SegmentIdServiceImpl implements SegmentIdService {
throw new RuntimeException(String.format("%s分段数据不存在,新增失败", bizTag));
}
}
// 在超时时间内根据表版本号字段(并发时重复更新)尝试获取新分段
long begin = ImClock.millis();
for (int i = 0; ; i++) {
if (i > 0) {
segmentId = segmentIdMapper.selectById(bizTag);
}
SegmentDTO segment = getSegment(segmentId, step);
SegmentRange segment = this.getSegment(segmentId, step);
if (segment != null) {
if (i > 0)
logger.info("{} get segment try {} times", bizTag, i);
@ -115,7 +116,7 @@ public class SegmentIdServiceImpl implements SegmentIdService {
return false;
}
try {
Thread.sleep(100);
Thread.sleep(ThreadLocalRandom.current().nextInt(50) + 50L);
} catch (InterruptedException e) {
logger.error("wait release redis lock interrupted!", e);
}
@ -128,7 +129,7 @@ public class SegmentIdServiceImpl implements SegmentIdService {
* @param dbSegmentId 当前DB segmentId
* @param step 步长
*/
private SegmentDTO getSegment(SegmentId dbSegmentId, Long step) {
private SegmentRange getSegment(SegmentId dbSegmentId, Long step) {
step = ObjectUtil.defaultValue(step, dbSegmentId.getInitStep(), dbSegmentId.getInitStep());
Preconditions.checkState(step > 0, "步长需大于0");
// 开始id, 结束id
@ -150,7 +151,7 @@ public class SegmentIdServiceImpl implements SegmentIdService {
return null;
}
// 更新成功
return new SegmentDTO()
return new SegmentRange()
.setBeginId(currentId)
.setEndId(endId);
}

6
im-logic/im-segment-id/src/test/java/net/sopod/soim/logic/segmentid/test/SegmentIdApplicationTest.java

@ -2,8 +2,8 @@ package net.sopod.soim.logic.segmentid.test;
import net.sopod.soim.common.util.ImClock;
import net.sopod.soim.logic.segmentid.SegmentIdApplication;
import net.sopod.soim.logic.segmentid.api.model.dto.SegmentDTO;
import net.sopod.soim.logic.segmentid.api.service.SegmentIdService;
import net.sopod.soim.logic.api.segmentid.model.SegmentRange;
import net.sopod.soim.logic.api.segmentid.service.SegmentIdService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -33,7 +33,7 @@ public class SegmentIdApplicationTest {
new Thread(() -> {
try {
long begin = ImClock.millis();
SegmentDTO segment = segmentIdService.nextSegmentId("im-user");
SegmentRange segment = segmentIdService.nextSegmentId("im-user");
System.out.println(idx + segment.toString() + (ImClock.millis() - begin));
}catch (Exception e){
System.out.println(idx + "fail:" + e.getMessage());

18
pom.xml

@ -113,12 +113,22 @@
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring-boot.version}</version>
<scope>compile</scope>
</dependency>
<dependency> <!-- 引入log4j2依赖 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
@ -134,6 +144,11 @@
<artifactId>lettuce-core</artifactId>
<version>6.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
@ -188,7 +203,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>

Loading…
Cancel
Save