一、SpringCloud搭建
SpringCloud的架构图:
nacos :
1、建立maven的父项目
①、cloud_01父项目
②、删除项目中的src(因为父项目没有代码,只需给子类提供依赖即可)
③、修改pom.xml
添加:
<packaging>pom</packaging>
<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>
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>
引入依赖
<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>
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
nacos中产生服务
②、新建消费者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
增加服务成功
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 "🍗";
}
}
运行结果:
②、消费者编写获得鸡腿方法 (跨服务器访问)
(1)pom.xml中增加依赖(负载均衡)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-loadbalancer</artifactId> </dependency>
(2)启动类添加注解
启动类增加域名访问对象
@LoadBalanced:达到负载均衡的能力
(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);
}
}
运行结果:
二、(消费者生产者)远程调用接口定义
现在的生产者(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)测试查看结果
说明传参能访问到数据了,
2、消费者(Consumer)
在网页上是能访问到数据的,但是我们需要在消费者(ConsumerController)里面访问到
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:远程通信
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
到现在为止,整个连接就写完了,直接调方法
只要参数是复杂对象,即使指定了是 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 调用成功";
}
}
测试结果
三、DTO层的构建
VO(View Object) :视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
DTO(Data Transfer Object):数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。
DO(Domain Object) :领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。
PO(Persistent Object) :持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性
消费者 远程调用 生产者 : 需要网络传输 , 使用 DTO 同一封装对象
原理与 SpringBoot 启动类相同
1. 将 DTO 对象封装到公共 DTO 模块
2. 为需要的项目引入公共 DTO 模块
注意点
1. 不需要继承父模块 ( 重复引用问题 )
2. 打包方式为 jar
3. 不需要添加启动类的编译
消费者调用生产者接口的时候还需要调用相关实体类,因此生产者和消费者的实体类一模一样,如果实体类过多,就会出现代码大量重复,因此,解决这一问题
把这些重复的类放到一个公共模块中,做成类似与启动器(web启动器:把某些功能封装在里面,要的时候就调用)的,在导入到这个类中
消费者(consumer)调用生产者(provider)的接口,生产者(provider)的接口需要一个user对象,消费者(consumer)在进行远程调用的时候需要把user对象传过去,服务之间互相调用使用DTO
所以,写一个公共模块,把所有的DTO封装起来
1、新建启动器
父项目上新建
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
②、父项目中进行依赖引入
pom.xml
<dependency>
<groupId>com.cloud01</groupId>
<artifactId>code</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
单方面认亲
③、查看provider和consumer是否都要DTO功能
consumer启动类查看是否有DTO功能:
现在代码中实体类只需要写一次了,
生产者是要去数据库那数据的所有实体类不能删除,而消费者是与生产者进行远程通信的,因而删除
将消费者(consumer)中的pojo软件包删除
FeignUserService中的User改为UserDto
测试是否出现问题:
两个实体类并不一致,但是并没有报错,因为是json接收,所以说前台与后台数据库并不一定保持一致
但是,我们的DTO并不完善,假如有一天DTO变了,因为Dto不可能只有账号密码这两个属性,假如增加了属性,User与DtoUser就会不匹配,所有修改生产者类的User改为UserDto
生产者类(provider)的User改为UserDto:
但是这样,假如属性多的话,数据就会很长 所有我们学习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();
}
③、生产者(provider)的UserController类进行注入
将UserDto填到User中去,将dto转成user对象
属性相同的情况:这个适应的是属性完全一模一样(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);
怕影响到的类的MapperFacade,因此:
单链模式改成原型链模式:
修改启动类: