Idea 搭建SpringCloud常见问题

675 阅读4分钟

前言

使用Idea搭建微服务开发环境时,我们多使用嵌套module来组织项目.如果配置不合适,会出现各种莫名其妙的问题.在此记录一下,避免以后重复趟坑.

问题1

  • SpringBoot started failed:Failed to introspect Class [org.springframework.boot.autoconfigure.xxx]
  • 分析: SpringCloud依赖SpringBoot,不同SpringCloud版本依赖特定的SpringBoot版本.因为我们使用嵌套的module来组织,所以很容易导致父子版本之间的版本冲突. 举例来说如果父pom文件引入了2.2.5 SpringBoot
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/>
    </parent>

我们知道parent项目包含了多个dependency,如果某个子module中单独引用了其中一个dependency,而且版本跟parent版本不一致,就会导致上面的问题.

  • 解决: 通过上面的异常信息我们可以看出是org.springframework.boot.autoconfigure相关问题.此时你可以删除这个依赖,默认使用父pom中的autoconfigure依赖

问题2

  • Failed to execute goal org.springframework.boot:spring-boot-maven-plugin
  • 分析: 一旦引入 spring-boot-maven-plugin,当打包时会扫描项目下的main方法,也就是说引入该配置,你就必须在项目src/main/java/下创建一个spring-boot启动类.
  • 解决: 如果父module下面没有代码,可以在pom中去掉plugin配置
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

问题3

  • Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.2.5.RELEASE
  • 分析: 注意dependencyManager中的dependencies只是声明,不会真的下载到本地. 如果子模块想引用某个依赖,需要在自己的dependencies里再次添加, 而且不用添加version信息,此时版本继承父模块版本. 当子模块明确需要跟父模块版本不一致是才需要添加version信息
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud-version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

默认情况下spring-boot-starter-xxx版本继承于spring-boot-starter-parent的版本,如果你不想继承parent版本,可以将scope设置为import. 这样你就可以使用自己的版本,子模块也将继承你自定义的版本.

注意: < scope> import < /scope> 只能在dependencyManagement 中使用

  • 解决: 在子模快的pom中显示添加需要的依赖,如果没有必要自定义版本可以不写版本,这样就继承了父pom中的版本.

问题4

  • 在UT中autowired标记的对象返回空
  • 分析autowired需要依赖Spring context才能运行,如果只是普通的测试类并不能加载Spring context
  • 解决: 为测试类添加如下注解
  1. @RunWith(SpringRunner.class) : 让Spring容器来启动该类
  2. @SpringBootTest: 将该测试类注册成bean
  • 示例:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestPaymentDao {
  @Autowired
  private PaymentDao paymentDao;

  @Test
  public void testAdd(){
    int result=paymentDao.add(new Payment(1L,"123"));
    Assert.assertTrue(result>0);
  }
}

问题5

  • org.yaml.snakeyaml.scanner.ScannerException: while scanning a simple key
  • 分析: 如果使用yml来作为配置文件,那么冒号后面必须有一个空格
  • 解决: 冒号后添加一个空格
  • 示例:
spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/blog?characterEncoding=utf-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false
    username: root
    password: 123456

在使用yml时一定要注意缩进,同一级的配置缩进保持一致,否则也会导致异常

问题6

  • SqlSession XXX was not registered for synchronization because synchronization is not active
  • 分析: 如果可以排除配置原因的话,可以尝试加上@Transactional注解
  • 解决: 为方法添加事务注解
  • 示例:
  @Update("update payment set sequence_num=#{SequenceNum} where id=#{Id}")
  @Transactional
  int update(Payment payment);

问题7

  • no converter found for return value of type
  • 分析: 使用RestTemplate将json转换为指定对象时,需要为指定对象的属性添加getter.因为我是用的是lombok,但是忘记了添加@getter,所以报以上错误
  • 解决: 添加@Getter@Data
  • 示例:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResult<T> {
  private int status;
  private String message;
  private T data;
  public ApiResult(int status,String message){
    this(status,message,null);
  }
}

问题8

  • RestApi之间相互调用的时候一般使用RestTemplate来传参,此时可能会出现被调用的api接收到的参数为空的情况
  • 举例: 订单服务的add方法会调用支付服务的add方法,并且需要向支付服务的add传递payment对象,此时支付服务接收到的payment可能为空 解决: 为支付服务add方法参数添加@RequestBody方法 示例:
  @PostMapping(value = "add")
  public ApiResult add(@RequestBody Payment payment){
    if(payment==null || payment.getId()==0)
      return new ApiResult(405,"invalid parameters");
    if(paymentService.add(payment)>0)
      return new ApiResult(200,"succeeded");
    return new ApiResult<>(500,"failed");
  }

问题9

  • Connect to localhost:8761 time out
  • 分析: 使用eureka搭建微服务时,需要通过defaultZone来指定注册中心的地址, 如果没有配置会发生上面的异常. 查看了我的配置,我的application.yml里包含了defaultZone,但是顶层的eureka写成了eurake,这样也间接导致找不到eureka.client.service-url.defaultZone
  • 解决: 将eurake 改成eureka. What a stupid mistake