SpringCloud的架构图:
nacos :
1、建立maven的父项目①、cloud_01父项目
②、删除项目中的src(因为父项目没有代码,只需给子类提供依赖即可)
③、修改pom.xml
添加:
pom
2.4.1 2020.0.0 2021.1
dependencyManagement里放依赖版本号
org.springframework.boot spring-boot-dependencies${spring-boot.version} pom import org.springframework.cloud spring-cloud-dependencies${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies${spring-cloud-alibaba.version} pom import
引入依赖
2、搭建子项目
org.springframework.boot spring-boot-starter-testorg.springframework.boot spring-boot-starter-webcom.alibaba.cloud spring-cloud-starter-alibaba-nacos-discoveryorg.springframework.cloud spring-cloud-starter-openfeignorg.projectlombok lombok
①、新建生产者provider
(1)、修改pom.xml
子项目(provider)继承父项目(cloud_01)
cloud_01 org.example 1.0-SNAPSHOT
父项目(cloud_01)承认子项目(provider)
provider
(2)、application.yml文件
spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 application: name: providerserver: port: 8081
(3)、启动类添加注解
开启服务发现
@EnableDiscoveryClient
nacos中产生服务
②、新建消费者consumer
(1)、修改pom.xml
子项目(consumer)继承父项目(cloud_01)
cloud_01 org.example 1.0-SNAPSHOT
父项目(cloud_01)承认子项目(consumer)
provider consumer
(2)、application.yml文件
spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 application: name: consumerserver: 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;@RestControllerpublic class ProviderController { @RequestMapping("/run") public String run(){ return ""; }}
运行结果:
②、消费者编写获得鸡腿方法 (跨服务器访问)
(1)pom.xml中增加依赖(负载均衡)
org.springframework.cloud spring-cloud-loadbalancer
(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;@RestControllerpublic 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
(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
③、启动类添加注解开启远程通信
增加这个注解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
测试结果
三、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"?>
把所有的实体类移上来
将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
com.cloud01 code0.0.1-SNAPSHOT
单方面认亲
③、查看provider和consumer是否都要DTO功能
consumer启动类查看是否有DTO功能:
现在代码中实体类只需要写一次了,
生产者是要去数据库那数据的所有实体类不能删除,而消费者是与生产者进行远程通信的,因而删除
将消费者(consumer)中的pojo软件包删除
FeignUserService中的User改为UserDto
测试是否出现问题:
两个实体类并不一致,但是并没有报错,因为是json接收,所以说前台与后台数据库并不一定保持一致
但是,我们的DTO并不完善,假如有一天DTO变了,因为Dto不可能只有账号密码这两个属性,假如增加了属性,User与DtoUser就会不匹配,所有修改生产者类的User改为UserDto
生产者类(provider)的User改为UserDto:
但是这样,假如属性多的话,数据就会很长 所有我们学习Orika框架
5、OrikaOrika:Orika对象复制教程(完美笔记) - 付宗乐 - 博客园
Orika 是 java Bean 映射框架,可以实现从一个对象递归拷贝数据至另一个对象。 在开发多层应用程序中非常有用。在这些层之间交换数据时,通常为了适应不同 API 需要转换一个实例至 另一个实例。①、生产者(provider)导入依赖
ma.glasnost.orika orika-core1.4.6
我们现在需要做的是,将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,因此:
单链模式改成原型链模式:
修改启动类: