一、SpringCloud搭建

SpringCloud的架构图:

springcloudalibaba项目父子pom_spring cloud

nacos

1、建立maven的父项目

①、cloud_01父项目

②、删除项目中的src(因为父项目没有代码,只需给子类提供依赖即可)

③、修改pom.xml

添加:

<packaging>pom</packaging>

springcloudalibaba项目父子pom_User_02

<spring-boot.version>2.4.1</spring-boot.version>
<spring-cloud.version>2020.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>

springcloudalibaba项目父子pom_spring cloud_03

dependencyManagement里放依赖版本号 

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>${spring-boot.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<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>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>${spring-cloud-alibaba.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

springcloudalibaba项目父子pom_ide_04

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--   远程通信-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

springcloudalibaba项目父子pom_spring_05

2、搭建子项目

①、新建生产者provider

(1)、修改pom.xml

子项目(provider)继承父项目(cloud_01)

<parent> <artifactId>cloud_01</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent>

父项目(cloud_01)承认子项目(provider)

<modules> <module>provider</module> </modules>

(2)、application.yml文件

spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 application: name: provider server: port: 8081

(3)、启动类添加注解

开启服务发现

@EnableDiscoveryClient

springcloudalibaba项目父子pom_spring cloud_06

nacos中产生服务

springcloudalibaba项目父子pom_ide_07

②、新建消费者consumer

(1)、修改pom.xml

子项目(consumer)继承父项目(cloud_01)

<parent> <artifactId>cloud_01</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent>

父项目(cloud_01)承认子项目(consumer

<modules> <module>provider</module> <module>consumer</module> </modules>

(2)、application.yml文件

spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 application: name: consumer server: port: 8082

(3)、启动类添加注解

开启服务发现

@EnableDiscoveryClient

增加服务成功

springcloudalibaba项目父子pom_java_08

3、调用方法

①、provider生产者编写生产鸡腿方法·

新建controller软件包,新建ProviderController

package com.provider.code.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProviderController {
    @RequestMapping("/run")
    public String run(){
        return "🍗";
    }
}
 运行结果:

springcloudalibaba项目父子pom_User_09

 

②、消费者编写获得鸡腿方法 (跨服务器访问

(1)pom.xml中增加依赖(负载均衡)

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-loadbalancer</artifactId> </dependency>

(2)启动类添加注解

启动类增加域名访问对象


@LoadBalanced:达到负载均衡的能力


springcloudalibaba项目父子pom_spring_10

(3)新建controller软件包,新建ConsumerController

package com.consumer.code.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class ConsumerController {
    private RestTemplate restTemplate;
    @Autowired
    public ConsumerController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }
    @RequestMapping("/run")
    public  String run(){
        return restTemplate.getForObject("http://provider/run",String.class);
    }
}

运行结果:

springcloudalibaba项目父子pom_User_11

  

二、(消费者生产者)远程调用接口定义

现在的生产者(provider)是没有接收参数的,返回String类型的

所以现在我们要是生产者接收参数

1、生产者(provider)

①、实体类

首先,写一个用户实体类

建立pojo软件包,新建User实体类

package com.provider.code.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@AllArgsConstructor
@NoArgsConstructor
@Data
//链式编程
@Accessors(chain=true)
public class User {

//    账号和密码
    private String account;
    private String password;
}

②、提供接口让别人来操纵用户实体类

使用UserController来构建一些有参数的东西

(1)UserController:

package com.provider.code.controller;

import com.provider.code.pojo.User;
import org.springframework.web.bind.annotation.*;
import sun.security.util.Password;

import java.util.Map;

@RestController
@RequestMapping("/user")
public class UserController {

//    接收别人传过来的账号 路径传值
    @RequestMapping("/{account}")
    public String getByPath(@PathVariable String account){
        System.out.println("account" + account);
        return "provider say : yes";
    }

//    请求直接携带 传两个参数
    @RequestMapping("/param")
    public String getParam(@RequestParam("account") String account,@RequestParam("password") String password){
        System.out.println("account" + account+"\tpassword"+password);
        return "provider say : yes";
    }

//    传json数据
    @RequestMapping("/pojo")
    public String getPojo(@RequestBody User user){
        System.out.println("pojo" + user);
        return "provider say : yes";
    }

//    传入map
    @RequestMapping("/more")
    public String getMore(@RequestBody Map<String,Object> map){
        System.out.println("more" + map);
        return "provider say : yes";
    }
}

(2)测试查看结果

springcloudalibaba项目父子pom_spring_12

 说明传参能访问到数据了,

2、消费者(Consumer)

在网页上是能访问到数据的,但是我们需要在消费者(ConsumerController)里面访问到

springcloudalibaba项目父子pom_spring_13

 RestTemplate能直接访问路径,路径带参直接/,请求带参直接?,但是json数据不好带,

所以修改上面的RestTemplate访问路径方法,直接方法访问

RestTemplate是用于两边进行通信(远程通信),它属于web启动器里面的

①、新建pojo软件包,新建user(与生产者user一模一样)

package com.consumer.code.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@AllArgsConstructor
@NoArgsConstructor
@Data
//链式编程
@Accessors(chain=true)
public class User {
//    账号和密码
    private String account;
    private String password;
}

②、新建service软件包,新建FeignUserService接口

使消费者直接调用生产者里面的接口

在消费者里面直接把生产者的接口定义出来

@FeignClient:远程通信

springcloudalibaba项目父子pom_ide_14

package com.consumer.code.service;
import com.consumer.code.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
//远程通信 与生产者进行远程通信
@FeignClient("provider")
@SuppressWarnings("all")
public interface FeignUserService {

//    接收别人传过来的账号 路径传值
    @RequestMapping("/user/{account}")
    String getByPath(@PathVariable(value = "account") String account);

//    请求直接携带 传两个参数
    @RequestMapping("/user/param")
    String getParam(@RequestParam("account") String account,@RequestParam("password") String password);

//    传json数据
    @RequestMapping("/user/pojo")
    String getPojo(@RequestBody User user);

//    传入map
    @RequestMapping("/user/more")
    String getMore(@RequestBody Map<String,Object> map);
}

③、启动类添加注解开启远程通信

增加这个注解FeignUserService就被托管了,

@EnableFeignClients

springcloudalibaba项目父子pom_spring_15

 到现在为止,整个连接就写完了,直接调方法


只要参数是复杂对象,即使指定了是 GET 方法, feign 依然会以 POST 方法进行发送请求,同时生产者必须支持POST 请求并给参数添加 @RequestBody 注解


④、controller包下新建UserController

消费者调用UserService接口

FeignClient接口,不能使用@GettingMapping之类的组合注解

FeignClient接口中,如果使用到@PathVariable必须指定其value

当使用feign传参数的时候,需要加上@RequestParam注解,否则对方服务无法识别参数


并使用 Feign 表示其需要远程对接的服务名称 , 并使用 @RequestMapping 表示其映射的


路径


package com.consumer.code.controller;
import com.consumer.code.pojo.User;
import com.consumer.code.service.FeignUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("user")
public class UserController {

    private FeignUserService service;
    @Autowired
    public UserController(FeignUserService service) {
        this.service = service;
    }

    @RequestMapping("test01")
    public String test01(String account){
        service.getByPath(account);
        return "yes 调用成功";
    }

    @RequestMapping("/{account}")
    public String test02(String account){
        service.getByPath(account);
        return "yes 调用成功";
    }

    @RequestMapping("test03")
    public String test03(String account,String password){
        service.getParam(account,password);
        return "yes 调用成功";
    }

    @RequestMapping("test04")
    public String test04(String account,String password){
        service.getPojo(new User().setAccount(account).setPassword(password));
        return "yes 调用成功";
    }

    @RequestMapping("test05")
    public String test05(String account,String password){
        Map<String,Object> map=new HashMap<>();
        map.put("account",account);
        map.put("password",password);
        service.getMore(map);
        return "yes 调用成功";
    }

}

测试结果

springcloudalibaba项目父子pom_java_16

 三、DTO层的构建

VO(View Object) :视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。

DTO(Data Transfer Object):数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。

DO(Domain Object) :领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。

PO(Persistent Object) :持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性

springcloudalibaba项目父子pom_User_17


消费者 远程调用 生产者 : 需要网络传输 , 使用 DTO 同一封装对象


原理与 SpringBoot 启动类相同

1. 将 DTO 对象封装到公共 DTO 模块

2. 为需要的项目引入公共 DTO 模块


注意点

1. 不需要继承父模块 ( 重复引用问题 )

2. 打包方式为 jar

3. 不需要添加启动类的编译

消费者调用生产者接口的时候还需要调用相关实体类,因此生产者和消费者的实体类一模一样,如果实体类过多,就会出现代码大量重复,因此,解决这一问题

把这些重复的类放到一个公共模块中,做成类似与启动器(web启动器:把某些功能封装在里面,要的时候就调用)的,在导入到这个类中

消费者(consumer)调用生产者(provider)的接口,生产者(provider)的接口需要一个user对象,消费者(consumer)在进行远程调用的时候需要把user对象传过去,服务之间互相调用使用DTO

所以,写一个公共模块,把所有的DTO封装起来

1、新建启动器

父项目上新建

springcloudalibaba项目父子pom_spring_18

2、修改pom.xml

①、不要继承父项目

②、删除test包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cloud01</groupId>
    <artifactId>code</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>commons</name>
    <description>Demo project for Spring Boot</description>

<!--    不要继承父项目-->

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.1</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3、新建dto软件包

 把所有的实体类移上来

将User改为UserDto,为了区分 

package com.cloud01.code.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@AllArgsConstructor
@NoArgsConstructor
@Data
//链式编程
@Accessors(chain=true)
public class UserDto {

//    账号和密码
    private String account;
    private String password;
}

4、引用Commons

因为provider和consumer都要引用Commons

①、将Commons导成jar

springcloudalibaba项目父子pom_spring cloud_19

 ②、父项目中进行依赖引入

pom.xml

<dependency>
    <groupId>com.cloud01</groupId>
    <artifactId>code</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
单方面认亲


③、查看provider和consumer是否都要DTO功能

consumer启动类查看是否有DTO功能:


springcloudalibaba项目父子pom_java_20

 现在代码中实体类只需要写一次了,

生产者是要去数据库那数据的所有实体类不能删除,而消费者是与生产者进行远程通信的,因而删除

将消费者(consumer)中的pojo软件包删除

springcloudalibaba项目父子pom_User_21


FeignUserService中的User改为UserDto


 测试是否出现问题:

springcloudalibaba项目父子pom_spring_22

两个实体类并不一致,但是并没有报错,因为是json接收,所以说前台与后台数据库并不一定保持一致

但是,我们的DTO并不完善,假如有一天DTO变了,因为Dto不可能只有账号密码这两个属性,假如增加了属性,User与DtoUser就会不匹配,所有修改生产者类的User改为UserDto

生产者类(provider)的User改为UserDto:

springcloudalibaba项目父子pom_spring cloud_23

 但是这样,假如属性多的话,数据就会很长 所有我们学习Orika框架

5、Orika

Orika 是 java Bean 映射框架,可以实现从一个对象递归拷贝数据至另一个对象。


在开发多层应用程序中非常有用。在这些层之间交换数据时,通常为了适应不同 API 需要转换一个实例至


另一个实例。


①、生产(provider)导入依赖

<dependency>    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.4.6</version>
</dependency>

我们现在需要做的是,将UserDto填到User中去,

②、生产(provider)的启动类增加方法

由于每次使用MapperFactory都需要new出来,所有增加到启动类中去,

@Beanpublic MapperFactory mapperFactory(){
   return new DefaultMapperFactory.Builder().build();
}

springcloudalibaba项目父子pom_spring cloud_24

 ③、生产者(provider)的UserController类进行注入


将UserDto填到User中去,将dto转成user对象


springcloudalibaba项目父子pom_User_25

属性相同的情况:这个适应的是属性完全一模一样(UserDto和User属性完全一样的) 

User map = factory.getMapperFacade().map(dto, User.class);

属性不同的情况:


field:里面放属性不同的


假设:生产者的账号改为name,而消费者的是account

factory.classMap(UserDto.class, User.class)        .field("name", "userName")
        .byDefault().register();
User user = factory.getMapperFacade().map(dto, User.class);

springcloudalibaba项目父子pom_java_26

怕影响到的类的MapperFacade,因此:

单链模式改成原型链模式:

修改启动类:

springcloudalibaba项目父子pom_spring_27