首页
归档
留言
友链
广告合作
壁纸
更多
美女主播
Search
1
博瑞GE车机升级/降级
5,576 阅读
2
Mac打印机设置黑白打印
4,886 阅读
3
修改elementUI中el-table树形结构图标
4,865 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,645 阅读
5
intelliJ Idea 2022.2.X破解
4,316 阅读
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Vue
Spring Cloud
Mac
MyBatis
WordPress
Element UI
Nacos
MacOS
Spring Cloud Alibaba
Mybatis-Plus
Typecho
jQuery
Java Script
asp.net
IntelliJ IDEA
MySQL
微信小程序
Sentinel
Laughing
累计撰写
608
篇文章
累计收到
1,421
条评论
首页
栏目
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
友链
广告合作
壁纸
美女主播
搜索到
94
篇与
的结果
2021-07-22
Spring Data Jpa Listener事件的扩展之自定义EntityListener
随着DDD的设计模式逐渐被大家认可和热捧。JPA通过Listener这种机制可以很好地实现事件分离、状体分离。假如,订单的状态变化可能对我们来说比较重要,我们需要定一个类去监听订单状态变更,通知相应的逻辑代码各自去干各自的活。新增一个UserAuditListener类,在相应的操作上添加Callbacks注解@Slf4j public class UserAuditListener { @PostPersist private void postPersist(SysUser sysUser) { recordLog(sysUser, OperateType.CREATE); } @PostRemove private void postRemove(SysUser sysUser) { recordLog(sysUser, OperateType.REMOVE); } @PostUpdate private void postUpdate(SysUser sysUser) { recordLog(sysUser, OperateType.UPDATE); } @PostLoad public void postLoad(SysUser sysUser) { recordLog(sysUser, OperateType.LOAD); } /** * 记录审计日志 * * @param sysUser 用户实体 * @param operateType 操作类型 */ private void recordLog(SysUser sysUser, OperateType operateType) { log.info("{}执行了{}操作", sysUser, operateType.getType()); } } enum OperateType { CREATE("创建"), UPDATE("更新"), REMOVE("删除"), LOAD("查询"); private final String type; OperateType(String type) { this.type = type; } public String getType() { return this.type; } }修改实体,增加@EntityListeners注解@Entity @Table(name = "sys_user") @Data @Slf4j @EntityListeners({UserAuditListener.class}) public class SysUser extends AbstractAuditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @JoinColumn(name = "dept_id", referencedColumnName = "dept_id") @ManyToOne(cascade = CascadeType.ALL) private SysDept sysDept; private String userName; private String nickName; private String userType; private String email; @Column(name = "phonenumber") private String phoneNumber; private String sex; private String avatar; @JsonIgnore private String password; @Enumerated(EnumType.STRING) private Status status; private String delFlag; private String loginIp; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp loginDate; private String remark; private Long recycleCompanyId; private Long medicalInstitutionId; private Long ethnicity; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "sys_user_role", joinColumns = {@JoinColumn(name = "user_id")}, inverseJoinColumns = {@JoinColumn(name = "role_id")}) private Set<SysRole> sysRoles; }测试这样,我们在增删改查方法调用时,就会执行我们对应的callback注解的方法比如我们插入用户@GetMapping("/save") public Long save(){ SysUser sysUser = new SysUser(); sysUser.setNickName("测试123321"); sysUser.setUserName("测试123321"); sysUser.setSex("0"); sysUserRepository.save(sysUser); return sysUser.getUserId(); }保存后,会自动调用postPersist方法
2021年07月22日
1,350 阅读
0 评论
1 点赞
2021-07-22
Spring Data Jpa扩展之Auditing
Auditing翻译过来是审计和审核。Spring的优秀之处在于帮我们想到了很多我们平时烦琐事情的解决方案,我们在实际的业务系统中,针对一张表的操作大部分是需要记录谁什么时间创建的,谁什么时间修改的,并且能让我们方便地记录操作日志。Spring Data JPA为我们提供了审计功能的架构实现,提供了4个注解专门解决这件事:@CreatedBy:创建人。@CreatedDate:创建时间。@LastModifiedBy:最后修改人。@LastModifiedDate:最后修改时间。增加公共虚拟类一般情况下,创建人、创建时间、最后修改人、最后修改时间四个字段都是统一的。所以,我们可以将其提取到公共的类中,然后所有的实体类继承这个虚拟类。@EntityListeners(AuditingEntityListener.class) @MappedSuperclass public abstract class AbstractAuditable { @CreatedBy private String createBy; @CreatedDate @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp createTime; @LastModifiedBy private String updateBy; @LastModifiedDate @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp updateTime; }[tag type="default"]必须添加@MappedSuperclass注解[/tag]修改实体,继承AbstractAuditable@Entity @Table(name = "sys_user") @Data @Slf4j public class SysUser extends AbstractAuditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @JoinColumn(name = "dept_id", referencedColumnName = "dept_id") @ManyToOne(cascade = CascadeType.ALL) private SysDept sysDept; private String userName; private String nickName; private String userType; private String email; @Column(name = "phonenumber") private String phoneNumber; private String sex; private String avatar; @JsonIgnore private String password; @Enumerated(EnumType.STRING) private Status status; private String delFlag; private String loginIp; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp loginDate; private String remark; private Long recycleCompanyId; private Long medicalInstitutionId; private Long ethnicity; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "sys_user_role", joinColumns = {@JoinColumn(name = "user_id")}, inverseJoinColumns = {@JoinColumn(name = "role_id")}) private Set<SysRole> sysRoles; }实现AbstractAuditable接口实现AbstractAuditable接口,提供创建者、最后修改者信息,这里我们随便模拟了以下常量。public class MyAuditorAware implements AuditorAware<String> { /** * Returns the current auditor of the application. * * @return the current auditor. */ @Override public Optional<String> getCurrentAuditor() { return Optional.of("测试用户"); } }开启Auditing功能通过@EnableJpaAuditing注解开启JPA的Auditing功能,并且告诉应用AuditorAware的实现类是谁。@SpringBootApplication @EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND) @EnableJpaAuditing public class Example1Application { public static void main(String[] args) { SpringApplication.run(Example1Application.class, args); } @Bean public AuditorAware<String> auditorAware(){ return new MyAuditorAware(); } }测试 @GetMapping("/save") public Long save(){ SysUser sysUser = new SysUser(); sysUser.setNickName("测试123321"); sysUser.setUserName("测试123321"); sysUser.setSex("0"); sysUserRepository.save(sysUser); return sysUser.getUserId(); }
2021年07月22日
1,265 阅读
0 评论
0 点赞
2021-07-22
Spring Data Jpa QueryByExampleExecutor及JpaSpecificationExecutor的用法
QueryByExampleExecutor可以通过实体进行简单的查询。比如我们查询用于昵称是超级开头的用户。@RestController @RequestMapping("/user") public class UserController { @Resource private SysUserRepository sysUserRepository; @GetMapping("/findAll") @Transactional public List<SysUser> findAll() { SysUser sysUser = new SysUser(); sysUser.setNickName("超级"); ExampleMatcher exampleMatcher = ExampleMatcher.matching() .withMatcher("nickName", ExampleMatcher.GenericPropertyMatchers.startsWith()) .withIgnorePaths("focus"); Example<SysUser> sysUserExample = Example.of(sysUser, exampleMatcher); return sysUserRepository.findAll(sysUserExample); } }其实跟MyBatis-Plus类似,功能比较简单,只能拼接and查询,一般使用不是很多。JpaSpecificationExecutor使用Specification的要点就是CriteriaBuilder,通过这个对象来创建条件,之后返回一个Predicate对象。这个对象中就有了相应的查询需求,我们同样可以定义多个Specification,之后通过Specifications对象将其连接起来。修改接口public interface SysUserRepository extends JpaRepository<SysUser, Long>, JpaSpecificationExecutor<SysUser> { }测试@RestController @RequestMapping("/user") public class UserController { @Resource private SysUserRepository sysUserRepository; @GetMapping("/findAll") @Transactional public List<SysUser> findAll() { return sysUserRepository.findAll((root, criteriaQuery, criteriaBuilder) -> { Predicate p1 = criteriaBuilder.like(root.get("nickName"), "%超级%"); Predicate p2 = criteriaBuilder.equal(root.get("userName"), "admin"); Predicate p3 = criteriaBuilder.equal(root.get("email"),"ry@163.com"); return criteriaBuilder.and(p1, p2); }); } }
2021年07月22日
1,238 阅读
0 评论
0 点赞
2021-07-21
Spring Data Jpa多表关联查询
在权限表中,我们一般会设计用户表、部门表、角色表,一般情况下,一个用户只能属于一个部门,但是一个用户能拥有多个角色,一个角色也可能对应多个部门。Spring Data Jpa中提供了多个注解,用于处理表之间的关联管理。常见注解@OneToOne@OneToOne代表一对一的关联关系,需要配合@JoinColumn一起使用。注意:可以双向关联,也可以只配置一方,需要视实际需求而定。@OneToOne注释五个属性:targetEntity、cascade、fetch、optional和mappedByfetch属性默认值是FetchType.EAGER。optional = true设置属性可以为null;targetEntity属性:Class类型的属性。定义关系类的类型,默认是该成员属性对应的类类型,所以通常不需要提供定义;cascade属性:CascadeType[]类型。该属性定义类和类之间的级联关系。定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操作,而且这种关系是递归调用的。cascade的值只能从CascadeType.PERSIST(级联新建)、CascadeType.REMOVE(级联删除)、CascadeType.REFRESH(级联刷新)、CascadeType.MERGE(级联更新)中选择一个或多个。还有一个选择是使用CascadeType.ALL,表示选择全部四项。@OneToMany、@ManyToOne@OneToMany、@ManyToOne代表一对多和多对一的关系,需要配合@JoinColumn一起使用。比如我们下面例子的用户与部门表,一个用户只属于一个部门,一个部门可以包含多个用户。@ManyToMany@ManyToMany代表多对多的关系。例如我们用于与角色的关联表,一般我们会设计一个用户、角色关联表的对应关系,配合@JoinTable一起使用。示例创建表部门表create table sys_dept ( dept_id bigint auto_increment comment '部门id' primary key, parent_id bigint default 0 null comment '父部门id', ancestors varchar(50) default '' null comment '祖级列表', dept_name varchar(30) default '' null comment '部门名称', order_num int default 0 null comment '显示顺序', leader varchar(20) null comment '负责人', phone varchar(11) null comment '联系电话', email varchar(50) null comment '邮箱', status char default '0' null comment '部门状态(0正常 1停用)', del_flag char default '0' null comment '删除标志(0代表存在 2代表删除)', create_by varchar(64) default '' null comment '创建者', create_time datetime null comment '创建时间', update_by varchar(64) default '' null comment '更新者', update_time datetime null comment '更新时间' ) comment '部门表';用户表create table sys_user ( user_id bigint auto_increment comment '用户ID' primary key, dept_id bigint null comment '部门ID', user_name varchar(30) not null comment '用户账号', nick_name varchar(30) not null comment '用户昵称', user_type varchar(2) default '00' null comment '用户类型(00系统用户)', email varchar(50) default '' null comment '用户邮箱', phonenumber varchar(11) default '' null comment '手机号码', sex char default '0' null comment '用户性别(0男 1女 2未知)', avatar varchar(100) default '' null comment '头像地址', password varchar(100) default '' null comment '密码', status varchar(5) default 'start' null comment '帐号状态(0正常 1停用)', del_flag char default '0' null comment '删除标志(0代表存在 2代表删除)', login_ip varchar(50) default '' null comment '最后登录IP', login_date datetime null comment '最后登录时间', create_by varchar(64) default '' null comment '创建者', create_time datetime null comment '创建时间', update_by varchar(64) default '' null comment '更新者', update_time datetime null comment '更新时间', remark longtext null comment '备注', recycle_company_id bigint null comment '所属回收单位', medical_institution_id bigint null comment '所属医疗机构', ethnicity bigint null comment '民族' ) comment '用户信息表';角色表create table sys_role ( role_id bigint auto_increment comment '角色ID' primary key, role_name varchar(30) not null comment '角色名称', role_key varchar(100) not null comment '角色权限字符串', role_sort int not null comment '显示顺序', data_scope char default '1' null comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', menu_check_strictly tinyint(1) default 1 null comment '菜单树选择项是否关联显示', dept_check_strictly tinyint(1) default 1 null comment '部门树选择项是否关联显示', status char not null comment '角色状态(0正常 1停用)', del_flag char default '0' null comment '删除标志(0代表存在 2代表删除)', create_by varchar(64) default '' null comment '创建者', create_time datetime null comment '创建时间', update_by varchar(64) default '' null comment '更新者', update_time datetime null comment '更新时间', remark varchar(500) null comment '备注' ) comment '角色信息表';用户角色关联表create table sys_user_role ( user_id bigint not null comment '用户ID', role_id bigint not null comment '角色ID', primary key (user_id, role_id) ) comment '用户和角色关联表';创建实体用户实体@Entity @Table(name = "sys_user") @Data @Slf4j public class SysUser { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long userId; @JoinColumn(name = "dept_id", referencedColumnName = "dept_id") @ManyToOne(cascade = CascadeType.ALL) private SysDept sysDept; private String userName; private String nickName; private String userType; private String email; @Column(name = "phonenumber") private String phoneNumber; private String sex; private String avatar; @JsonIgnore private String password; @Enumerated(EnumType.STRING) private Status status; private String delFlag; private String loginIp; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp loginDate; private String createBy; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp createTime; private String updateBy; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp updateTime; private String remark; private Long recycleCompanyId; private Long medicalInstitutionId; private Long ethnicity; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "sys_user_role", joinColumns = {@JoinColumn(name = "user_id")}, inverseJoinColumns = {@JoinColumn(name = "role_id")}) private Set<SysRole> sysRoles; } 部门实体@Entity @Table(name = "sys_dept") @Getter @Setter @ToString @RequiredArgsConstructor public class SysDept { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "dept_id") private long deptId; private long parentId; private String ancestors; private String deptName; private long orderNum; private String leader; private String phone; private String email; private String status; private String delFlag; private String createBy; private java.sql.Timestamp createTime; private String updateBy; private java.sql.Timestamp updateTime; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; SysDept sysDept = (SysDept) o; return Objects.equals(deptId, sysDept.deptId); } @Override public int hashCode() { return 866095534; } } 角色实体@Entity @Data public class SysRole { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long roleId; private String roleName; private String roleKey; private long roleSort; private String dataScope; private long menuCheckStrictly; private long deptCheckStrictly; private String status; private String delFlag; private String createBy; private java.sql.Timestamp createTime; private String updateBy; private java.sql.Timestamp updateTime; private String remark; } 用户角色关联实体@Entity @IdClass(SysUserRole.class) @Data public class SysUserRole implements Serializable { @Id @Column(name = "user_id") private long userId; @Id @Column(name = "role_id") private long roleId; }Jpa查询接口这个接口不是必须的,可以调用任意的Crud接口。public interface SysUserRepository extends JpaRepository<SysUser, Long> { /** * 根据用户名称查找列表 * * @param userName 用户名称 * @return 列表 */ List<SysUser> findByUserName(String userName); // @Query("select u from SysUser u") Stream<SysUser> readAllBy(); Stream<SysUser> streamAllBy(); @Async Future<List<SysUser>> findAllBy(); @Query("select u from SysUser u where u.userName = :userName and u.email = :email") SysUser findByUserNameAndEmail(@Param("userName") String userName, @Param("email") String email); @Query("select u from SysUser u where u.nickName like %:nickName%") List<SysUser> findAllByNickName(@Param("nickName") String nickName); @Query(value = "select * from sys_user u where email like concat('%',:email,'%')", nativeQuery = true) List<SysUser> findAllByEmailLike(@Param("email") String email); @Query(value = "select u from SysUser u") Page<SysUser> findAllBy(Pageable pageable); SysUser findFirstByUserId(Long userId); @Modifying(clearAutomatically = true) @Query(value = "update SysUser set remark = '234' where userId = :userId") int setRemarkByUserId(@Param("userId") Long userId); }测试@RestController @RequestMapping("/user") public class UserController { @Resource private SysUserRepository sysUserRepository; @GetMapping("/findAll") @Transactional public SysUser findAll() { return sysUserRepository.findFirstByUserId(111L); } }
2021年07月21日
1,374 阅读
0 评论
0 点赞
2021-07-21
Spring Data Jpa使用枚举
有时候在数据库设计的时候,我们可能会使用一些固定的值,比如启用、停用,只有这两个值,我们设计数据库可能设计成stop、start。这个时候,我们在Jpa中设计实体时,一般设置成枚举就更合理了。创建枚举public enum Status { stop("停用"), start("启用"); private String value; Status(String value) { this.value = value; } }实体@Entity @Table(name = "sys_user") @Data @Slf4j public class SysUser { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long userId; private Long deptId; private String userName; private String nickName; private String userType; private String email; @Column(name = "phonenumber") private String phoneNumber; private String sex; private String avatar; @JsonIgnore private String password; @Enumerated(EnumType.STRING) private Status status; private String delFlag; private String loginIp; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp loginDate; private String createBy; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp createTime; private String updateBy; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp updateTime; private String remark; private Long recycleCompanyId; private Long medicalInstitutionId; private Long ethnicity; } 查询我们可以查看一些查询的结果
2021年07月21日
1,015 阅读
0 评论
0 点赞
2021-07-21
Spring Data Jpa流查询
Spring Data Jpa可以通过使用Java 8 Stream作为返回类型,来逐步处理查询方法的结果,而不是简单的将查询结果包装在Stream数据存储中。接口定义public interface SysUserRepository extends JpaRepository<SysUser, Long> { /** * 根据用户名称查找列表 * * @param userName 用户名称 * @return 列表 */ List<SysUser> findByUserName(String userName); // @Query("select u from SysUser u") Stream<SysUser> readAllBy(); Stream<SysUser> streamAllBy(); }使用@RestController @RequestMapping("/user") public class UserController { @Resource private SysUserRepository sysUserRepository; @GetMapping("/findAll") @Transactional public List<SysUser> findAll() { try (Stream<SysUser> sysUserStream = sysUserRepository.streamAllBy()) { return sysUserStream.collect(Collectors.toList()); } catch (Exception ex) { ex.printStackTrace(); return new ArrayList<>(); } } }注意事项必须通过try catch关闭流方法必须有@Transactional注解
2021年07月21日
1,658 阅读
0 评论
0 点赞
2021-07-20
docker部署ActiveMQ并整合到Spring Boot
ActiveMQ是由Apache基金会采用Java语言开发的一个开源的消息中间件,完美的遵循JMS规范。ActiveMQ易于实现高级场景,而且只需要付出低消耗。被誉为消息中间件的“瑞士军刀”。常用来处理高并发请求的流量削峰、事务处理。本文,我们通过docker部署ActiveMQ,然后结合ActiveMQ的queue及topic模式,分别介绍在spring boot中的应用。通过docker安装ActiveMQ搜索可用的ActiveMQdocker search activemq我这里下载评星最高的webcenter/activemq下载docker镜像docker pull webcenter/activemq运行dockerdocker run -d --name activemq -p 61616:61616 -p 8161:8161 webcenter/activemq检查是否运行成功打开http://139.198.172.114:8161/,出现以下页面可以点击Manage ActiveMQ broker,默认用户名、密码都是adminSpring Boot整合ActiveMQActiveMQ在实际企业开发中主要有两种模式:点对点模式(Queue)和发布订阅模式(Topic)Queue模式Queue模式即队列(先进先出),消息提供者生产消息发布到Queue中,然后消费者从Queue中取出,并消费消息。这里需要注意的是,消息被消费者消费之后,Queue不再存储,所以消息只能被消费一次。Queue支持存在多个消息消费者,但是对一个消息而言,只会有一个消费者可以消费。创建Spring Boot项目并添加依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-pool</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>定义常量Constants.javapublic class Constants { public final static String QUEUEMESSAGE = "queue"; }添加配置类ActiveMQConfig.java@Configuration public class ActiveMQConfig { @Bean public Queue queue(){ return new ActiveMQQueue(Constants.QUEUEMESSAGE); } }创建消息提供者@RestController public class ProducerController { @Resource private JmsMessagingTemplate jmsMessagingTemplate; @Resource private Queue queue; @RequestMapping("/sendQueueMessage/{msg}") public String sendQueueMessage(@PathVariable("msg") String msg) { this.jmsMessagingTemplate.convertAndSend(queue, msg); return msg; } }配置ActiveMQ修改项目配置文件,配置ActiveMQspring.activemq.broker-url=tcp://139.198.172.114:61616 #接收所有消息 spring.activemq.packages.trust-all=true创建消息消费者Consumer.java@Component @Slf4j public class Consumer { @JmsListener(destination = Constants.QUEUEMESSAGE) public void receiveQueueMessage1(String message) { if (null != message) { log.info("收到queue报文1:" + message); } } @JmsListener(destination = Constants.QUEUEMESSAGE) public void receiveQueueMessage2(String message) { if (null != message) { log.info("收到queue报文2:" + message); } } }测试我们访问http://localhost:8083/sendQueueMessage/123,可以查看控制台输出信息可以看到,虽然我们提供了两个消费者,但是实际上只有一个消费者消费成功。Topic模式消息提供者将消息发布到Topic中,同时有多个消费者(订阅者)消费该消息,与Queue模式不同,发布到Topic消息会被所有的订阅者消费。Topic模式与Queue模式类似,我们这里基于Queue模式的代码进行修改。修改配置类ActiveMQConfig.java增加Topic的Bean,修改后代码如下@Configuration public class ActiveMQConfig { @Bean public Queue queue(){ return new ActiveMQQueue(Constants.QUEUEMESSAGE); } @Bean public Topic topic(){ return new ActiveMQTopic(Constants.TOPICMESSAGE); } }修改消息消费者@RestController public class ProducerController { @Resource private JmsMessagingTemplate jmsMessagingTemplate; @Resource private Queue queue; @Resource private Topic topic; @RequestMapping("/sendQueueMessage/{msg}") public String sendQueueMessage(@PathVariable("msg") String msg) { this.jmsMessagingTemplate.convertAndSend(queue, msg); return msg; } @RequestMapping("/sendTopicMessage/{msg}") public String sendTopicMessage(@PathVariable("msg") String msg) { this.jmsMessagingTemplate.convertAndSend(topic, msg); return msg; } }修改消费者@Component @Slf4j public class Consumer { @JmsListener(destination = Constants.QUEUEMESSAGE) public void receiveQueueMessage1(String message) { if (null != message) { log.info("收到queue报文1:" + message); } } @JmsListener(destination = Constants.QUEUEMESSAGE) public void receiveQueueMessage2(String message) { if (null != message) { log.info("收到queue报文2:" + message); } } @JmsListener(destination = Constants.TOPICMESSAGE) public void receiveTopicMessage1(String message) { if (null != message) { log.info("收到topic报文1:" + message); } } @JmsListener(destination = Constants.TOPICMESSAGE) public void receiveTopicMessage2(String message) { if (null != message) { log.info("收到topic报文2:" + message); } } }修改项目配置文件通过spring.jms.pub-sub-domain=true配置启用Topic。server.port=8083 spring.activemq.broker-url=tcp://139.198.172.114:61616 #接收所有消息 spring.activemq.packages.trust-all=true #配置使用topic spring.jms.pub-sub-domain=true测试访问http://localhost:8083/sendTopicMessage/123,查看控制台。可以看到两个订阅者都输出了消息。ActiveMQ监控我们通过http://139.198.172.114:8161/admin/topics.jsp,也可以查看服务器的信息
2021年07月20日
862 阅读
0 评论
0 点赞
2021-07-20
使用docker部署dubbo/dubbox并实现服务提供者、消费者
实现目标基于centos 7,通过docker打包部署dubbox(2.8.4),并实现简单的服务提供者与消费者。前置条件服务器安装JDK1.8并正确配置环境变量开发环境安装JDK1.8并正确配置环境变量开发环境安装maven并正确配置环境变量(建议使用阿里云仓库)安装zookeeperZooKeeper 是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。下载压缩包下载压缩包curl -O https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-3.7.0/apache-zookeeper-3.7.0-bin.tar.gz解压压缩包tar -zxvf apache-zookeeper-3.7.0-bin.tar.gz修改zookeeper配置文件进入zookeeper配置文件所在目录cd /home/apache-zookeeper-3.7.0-bin/conf修改配置文件cp zoo_sample.cfg zoo.cfg运行zookeepercd /home/apache-zookeeper-3.7.0-bin/bin ./zkServer.sh start检查zookeeper运行状态cd /home/apache-zookeeper-3.7.0-bin/bin ./zkServer.sh statusdubbox打包Dubbox 是一个分布式服务框架,其前身是阿里巴巴开源项目Dubbo ,被国内电商及互联网项目中使用,后期阿里巴巴停止了该项目的维护,当当网便在Dubbo基础上进行优化,并继续维护,为了与原有的Dubbo区分,故将其命名为Dubbox。Dubbox 致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbox就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbox这样的分布式服务框架的需求,并且本质上是个服务调用的东东,说白了就是个远程服务调用的分布式框架。dubbox没有提供打包好的jar包或者war包,我们需要自己在github下载源码并编译。下载dubbox源码dubbox代码下载地址:https://github.com/dangdangdotcom/dubbox编译代码进入dubbox代码文件见,执行打包命令mvn install -D maven.test.skip=true打包完成后,在dubbo-admin\target中能看到一个dubbo-admin-2.8.4.war修改dubbox配置文件我们用压缩软件打开dubbo-admin-2.8.4.war,打开WEB-INF\dubbo.properties,修改zookeeper地址及对应用户密码,如下:dubbo.registry.address=zookeeper://IP地址:2181 dubbo.admin.root.password=root dubbo.admin.guest.password=guest[tag type="default"]注意修改成自己zookeeper对应的ip地址[/tag]war包修改完成后备用。安装docker我们通过docker部署dubbox。更新yumyum -y update安装依赖 yum install -y yum-utils device-mapper-persistent-data lvm2安装dockeryum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum install docker-ce-18.06.0.ce-3.el7启动docker并配置开机启动systemctl start docker systemctl enable docker配置docker为国内源vi /etc/docker/daemon.json输入以下内容{ "registry-mirrors": ["http://hub-mirror.c.163.com", "https://docker.mirrors.ustc.edu.cn"] }通过docker部署dubbox我们这里自己打包一个docker,并部署dubbox创建文件夹创建一个文件夹/home/makedocker,用于存储Dockerfile以及war包。mkdir /home/makedocker cd /home/makedocker将war包上传到/home/makedocker制作Dockerfilevi Dockerfile并输入以下内容FROM registry.cn-hangzhou.aliyuncs.com/shuodao/tomcat-8.5.27 MAINTAINER Laughing COPY dubbo.war /usr/tomcat/webapps/ EXPOSE 8080/tcp打包dockerdocker build -t tomcat-dubbo .启动dockerdocker run -d -p 8888:8080 --name tomcat-dubbo tomcat-dubbo查看docker运行状态docker ps显示以下内容,代表docker运行成功查看dubbox运行状态打开http://139.198.172.114:8888/dubbo,系统要求输入密码,密码为dubbo.properties中配置的密码,输入完成后,正常打开界面如下实现dubbox服务我们创建一个用户服务,作为服务提供者,判断用户是否登录。一个商品服务,作为服务消费者,调用用户是否登录的接口。实现通用服务接口创建一个maven项目,提供基本的接口定义。pom.xml内容如下<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cc.lisen</groupId> <artifactId>dubbo-common</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> </project>创建接口package cc.lisen.dubbo.common.service; /** * 博客:https://lisen.cc * Description: * * @Author: 李森的博客 * DateTime: 2021-07-19 20:20 */ public interface RpcUserService { boolean checkUserLogin(String userName); }将代码部署到本地maven仓库由于我们没有远程maven仓库,所以我们将jar包install到本地仓库。在代码根目录,执行以下命令,打包到本地仓库。mvn install -D maven.test.skip=true实现服务提供者创建服务提供者。pom.xml如下<?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cc.lisen</groupId> <artifactId>dubbo-provider</artifactId> <version>0.0.1-SNAPSHOT</version> <name>dubbo-provider</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.8.4</version> </dependency> <dependency> <groupId>io.dubbo.springboot</groupId> <artifactId>spring-boot-starter-dubbo</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>cc.lisen</groupId> <artifactId>dubbo-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <includeSystemScope>true</includeSystemScope> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>实现接口package cc.lisen.dubboprovider.service; import cc.lisen.dubbo.common.service.RpcUserService; import org.springframework.stereotype.Service; /** * 博客:https://lisen.cc * Description: * * @Author: 李森的博客 * DateTime: 2021-07-19 19:56 */ @Service @com.alibaba.dubbo.config.annotation.Service(interfaceClass = RpcUserService.class) public class RpcUserServiceImpl implements RpcUserService{ @Override public boolean checkUserLogin(String userName){ return "admin".equals(userName); } } 修改配置文件配置dubboserver.port=8081 #dubbo提供者的别名,只是个标识 spring.dubbo.application.name=dubbo-provider #zookeeper地址 spring.dubbo.registry.address=zookeeper://139.198.172.114:2181 #dubbo协议 spring.dubbo.protocol.name=dubbo #dubbo端口号 spring.dubbo.protocol.port=20880 #这是你要发布到dubbo的接口所在包位置 spring.dubbo.scan=cc.lisen.dubboprovider.service测试运行项目,然后我们打开dubbo服务端,查看是否注册成功。实现服务消费者创建服务消费者。pom.xml如下<?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cc.lisen</groupId> <artifactId>dubbo-provider</artifactId> <version>0.0.1-SNAPSHOT</version> <name>dubbo-provider</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.8.4</version> </dependency> <dependency> <groupId>io.dubbo.springboot</groupId> <artifactId>spring-boot-starter-dubbo</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>cc.lisen</groupId> <artifactId>dubbo-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <includeSystemScope>true</includeSystemScope> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project> 创建商品服务package cc.lisen.dubboprovider.service; /** * 博客:https://lisen.cc * Description: * * @Author: 李森的博客 * DateTime: 2021-07-19 20:36 */ public interface GoodService { boolean checkUserLogin(String userName); }创建商品服务类,调用用户登录判断接口package cc.lisen.dubboprovider.service.impl; import com.alibaba.dubbo.config.annotation.Reference; import cc.lisen.dubbo.common.service.RpcUserService; import cc.lisen.dubboprovider.service.GoodService; import org.springframework.stereotype.Service; /** * 博客:https://lisen.cc * Description: * * @Author: 李森的博客 * DateTime: 2021-07-19 19:56 */ @Service public class GoodServiceImpl implements GoodService { @Reference private RpcUserService rpcUserService; @Override public boolean checkUserLogin(String userName) { return rpcUserService.checkUserLogin(userName); } } 测试消费者服务是否正常注册测试创建商品销售接口package cc.lisen.dubboprovider.controller; import cc.lisen.dubboprovider.service.GoodService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * 博客:https://lisen.cc * Description: * * @Author: 李森的博客 * DateTime: 2021-07-19 20:40 */ @RestController public class GoodController { @Resource private GoodService goodService; @GetMapping("sell/good") public boolean sellGood(){ return goodService.checkUserLogin("admin"); } } 测试接口是否正常调用
2021年07月20日
950 阅读
0 评论
1 点赞
2021-07-18
使用Hystrix实现微服务的熔断处理
Hystrix是Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失效,从而提升系统的可用性与容错性。Hystrix基本使用添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.8.RELEASE</version> </dependency>为启动类增加@EnableHystrix注解,从而为项目启用断路器支持@SpringBootApplication @EnableFeignClients @EnableHystrix public class MicroserviceConsumerMovieApplication { public static void main(String[] args) { SpringApplication.run(MicroserviceConsumerMovieApplication.class, args); } }修改方法,增加容错能力 @GetMapping("/user/{id}") @HystrixCommand(fallbackMethod = "findByIdFallback", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"), @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "10000") }, threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "1"), @HystrixProperty(name = "maxQueueSize", value = "10") }) public User findById(@PathVariable Long id) { return userFeignClient.findById(id); } public User findByIdFallback(Long id, Throwable throwable) { log.info(throwable.getMessage()); User user = new User(); user.setName("默认用户"); return user; }测试我们断开此时的服务提供者,访问方法,可以看到进入了容错方法,并获取到了异常信息。feign集成hystrix[alt type="success"]关于soring Cloud版本提示[/alt]一定要注意Spring Cloud版本问题,Spring Cloud 2020 后,开启feign的hystrix支持需要在配置文件配置如下feign: circuitbreaker: enabled: true早期版本的Spring Cloud需要配置如下feign: hystrix: enabled: true为feign添加回退通过fallback添加回退通过fallback添加回退,不能获取回退原因。@FeignClient(name = "microservice-provider-user",fallback = UserFeignClientCallback.class) public interface UserFeignClient { /** * 使用Feign自带的注解 @RequestLine * @param id * @return */ @RequestLine("GET /{id}") User findById(@Param("id") Long id); } @Slf4j @Component class UserFeignClientCallback implements UserFeignClient{ /** * 使用Feign自带的注解 @RequestLine * * @param id * @return */ @Override public User findById(Long id) { User user = new User(); user.setName("默认用户"); return user; } }通过fallbackFactory添加回退fallbackFactory是fallback的升级版本,能够获取到回退原因。@FeignClient(name = "microservice-provider-user",fallbackFactory = UserFeignClientCallbackFactory.class) public interface UserFeignClient { /** * 使用Feign自带的注解 @RequestLine * @param id * @return */ @RequestLine("GET /{id}") User findById(@Param("id") Long id); } @Slf4j @Component class UserFeignClientCallbackFactory implements FallbackFactory<UserFeignClient> { @Override public UserFeignClient create(Throwable cause) { return new UserFeignClient() { @Override public User findById(Long id) { log.info(cause.getMessage()); User user = new User(); user.setName("默认用户"); return user; } }; } }[alt type="success"]温馨提示[/alt]日志最好放到各个fallback方法中,而不要放到create方法中,否则在引用启动时,会打印日志。feign项目中Hystrix的监控首先需要添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>启动类添加@EnableHystrix@SpringBootApplication @EnableFeignClients @EnableHystrix public class MicroserviceConsumerMovieApplication { public static void main(String[] args) { SpringApplication.run(MicroserviceConsumerMovieApplication.class, args); } }修改配置文件,暴漏地址management: endpoints: web: exposure: include: hystrix.stream测试打开地址http://localhost:8081/actuator/hystrix.stream使用hystrix-dashboard可视化监控项目我们刚才项目监控时,返回的都是Json数据,通过hystrix-dashboard我们可以实现可视化的监控。添加hystrix-dashboard依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> <version>2.2.8.RELEASE</version> </dependency>在启动类添加@EnableHystrixDashboard注解@SpringBootApplication @EnableFeignClients @EnableHystrix @EnableHystrixDashboard public class MicroserviceConsumerMovieApplication { public static void main(String[] args) { SpringApplication.run(MicroserviceConsumerMovieApplication.class, args); } }修改配置文件配置文件添加hystrix: dashboard: proxy-stream-allow-list: "*"如果不添加,监控会提示Unable to connect to Command Metric Stream.测试打开http://localhost:8081/hystrix,监控地址输入http://localhost:8081/actuator/hystrix.stream,title随便输入即可。点击Monitor Stream,然后访问任意的接口,系统输出界面如下
2021年07月18日
1,020 阅读
0 评论
1 点赞
2021-07-18
Spring Cloud 使用Feign实现声明式Rest调用
Feign是Netflix开发的声明式、模块化的HTTP客户端。Feign可以帮我们更加便捷、优雅的调用HTTP API。在Spring Cloud中,使用Feign非常简单,创建一个接口,并在接口上添加一些注解,代码就完成了。基本使用添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> 增加配置文件配置文件与之前类似,不再赘诉。server: port: 8081 eureka: instance: prefer-ip-address: false client: service-url: defaultZone: http://peer1:8761/eureka,http://peer2:8762/eureka spring: application: name: microservice-consumer-movie修改启动类,增加@EnableFeignClients@SpringBootApplication @EnableFeignClients public class MicroserviceConsumerMovieApplication { public static void main(String[] args) { SpringApplication.run(MicroserviceConsumerMovieApplication.class, args); } }增加Feign接口/** * 博客:https://lisen.cc * Description: * * @Author: 香草物语 * DateTime: 2021-07-18 12:52 */ @FeignClient(name = "microservice-provider-user") public interface UserFeignClient { @RequestMapping(value = "{id}",method = RequestMethod.GET) User findById(@PathVariable Long id); } 调用@RestController public class MovieController { @Resource private DiscoveryClient discoveryClient; // @Resource // private RestTemplate restTemplate; @Resource private UserFeignClient userFeignClient; // @Resource // private LoadBalancerClient loadBalancerClient; @GetMapping("/user-instance") public List<ServiceInstance> showInfo() { return discoveryClient.getInstances("microservice-provider-user"); } @GetMapping("/user/{id}") public User findById(@PathVariable Long id) { // return restTemplate.getForObject("http://microservice-provider-user/"+id, User.class); return userFeignClient.findById(id); } // @GetMapping("log/microservice-provider-user") // public ServiceInstance userLog() { // return loadBalancerClient.choose("microservice-provider-user"); // } }基于代码Feign自定义配置创建配置类创建Feign的配置类UserFeignConfig,需要注意的是,该类不能添加@Configuration注解,如果添加了@Configuration注解,那么该类不能在主应用程序的@ComponentScan注解扫描包内。/** * 博客:https://lisen.cc * Description:feign配置文件 * * @Author: 香草物语 * DateTime: 2021-07-18 15:39 */ public class UserFeignConfig { /** * 将契约改成Feign原生的契约,这样就可以使用Feign自带的注解了 * @return */ @Bean public Contract feignContract(){ return new Contract.Default(); } }修改Feign接口/** * 博客:https://lisen.cc * Description: * * @Author: 香草物语 * DateTime: 2021-07-18 12:52 */ @FeignClient(name = "microservice-provider-user",configuration = UserFeignConfig.class) public interface UserFeignClient { /** * 使用Feign自带的注解 @RequestLine * @param id * @return */ @RequestLine("GET /{id}") User findById(@Param("id") Long id); }测试@RestController public class MovieController { @Resource private DiscoveryClient discoveryClient; // @Resource // private RestTemplate restTemplate; @Resource private UserFeignClient userFeignClient; // @Resource // private LoadBalancerClient loadBalancerClient; @GetMapping("/user-instance") public List<ServiceInstance> showInfo() { return discoveryClient.getInstances("microservice-provider-user"); } @GetMapping("/user/{id}") public User findById(@PathVariable Long id) { // return restTemplate.getForObject("http://microservice-provider-user/"+id, User.class); return userFeignClient.findById(id); } // @GetMapping("log/microservice-provider-user") // public ServiceInstance userLog() { // return loadBalancerClient.choose("microservice-provider-user"); // } }再次访问http://localhost:8081/user/1,查看输出全局配置以上我们是根据服务进行配置的,我们也可以进行全局配置。在启动类中添加@EnableFeignClients注解@SpringBootApplication @EnableFeignClients(defaultConfiguration = UserFeignConfig.class) public class MicroserviceConsumerMovieApplication { public static void main(String[] args) { SpringApplication.run(MicroserviceConsumerMovieApplication.class, args); } } 使用属性自定义Feign配置配置指定名称的Feign Client修改配置文件server: port: 8081 eureka: instance: prefer-ip-address: false client: service-url: defaultZone: http://peer1:8761/eureka,http://peer2:8762/eureka spring: application: name: microservice-consumer-movie feign: client: config: microservice-provider-user: contract: feign.Contract.Default修改Feign接口@FeignClient(name = "microservice-provider-user") public interface UserFeignClient { /** * 使用Feign自带的注解 @RequestLine * @param id * @return */ @RequestLine("GET /{id}") User findById(@Param("id") Long id); }配置全局Feign Client修改配置文件,将指定名称的Feign Client改成default即可。server: port: 8081 eureka: instance: prefer-ip-address: false client: service-url: defaultZone: http://peer1:8761/eureka,http://peer2:8762/eureka spring: application: name: microservice-consumer-movie feign: client: config: default: contract: feign.Contract.DefaultFeign对压缩的支持可以通过以下配置对请求或相应进行压缩。feign: compression: request: enabled: true mime-types: text/xml,application/xml,application/json min-request-size: 2048 response: enabled: true配置Feign日志feign: client: config: default: contract: feign.Contract.Default logging-level: full compression: request: enabled: true mime-types: text/xml,application/xml,application/json min-request-size: 2048 response: enabled: true logging: level: cc.lisen.microserviceconsumermovie.service.UserFeignClient: debug
2021年07月18日
972 阅读
0 评论
0 点赞
2021-07-17
Spring Cloud 服务消费者通过ribbon调用服务提供者
在Spring Cloud Eureka配置集群中,我们介绍了Eureka集群部署的方式。在[pring Cloud Eureka消费者获取服务者信息](https://lisen.cc/back/spring-cloud-eureka-consumer-access-to-service-provider-information.html)中,我们介绍了Eureka集群部署的方式,)中,我们介绍了Eureka服务消费者获取服务提供者信息的方式。这篇文章,我通过ribbon的方式,介绍服务消费者调用服务提供者的方式。服务提供者代码无需改动,我们只基于microservice-consumer-movie增加调用服务提供者的相关代码。基本调用方式修改启动类主要是提供RestTemplate相关Bean,增加@LoadBalanced注解使其具备负载均衡能力。@SpringBootApplication public class MicroserviceConsumerMovieApplication { public static void main(String[] args) { SpringApplication.run(MicroserviceConsumerMovieApplication.class, args); } @Bean @LoadBalanced //基于rest+ribbon的调用方式需要加此注解 public RestTemplate restTemplate(){ return new RestTemplate(); } }调用@RestController public class MovieController { @Resource private DiscoveryClient discoveryClient; @Resource private RestTemplate restTemplate; @GetMapping("/user-instance") public List<ServiceInstance> showInfo(){ return discoveryClient.getInstances("microservice-provider-user"); } @GetMapping("/user/{id}") public User findById(@PathVariable Long id){ return restTemplate.getForObject("http://microservice-provider-user/"+id, User.class); } }其他配置其他配置比如依赖、配置文件,请参考之前的文章。测试访问http://localhost:8081/user/1,正确返回用户信息负载均衡算法Ribbon是Netflix发布的负载均衡器,他有助于控制HTTP和TCP客户端的行为。Ribbon默认为我们提供了很多负载均衡算法,比如轮询、随机等,我们也可为Ribbon实现自定义的轮询算法。使用默认轮询算法测试启动两个客户端还是使用前面的用户服务,我们启动两个服务提供者。java -jar microservice-simple-provider-user-0.0.1-SNAPSHOT.jar --server.port=9001 java -jar microservice-simple-provider-user-0.0.1-SNAPSHOT.jar --server.port=9002此时查看Eureka Server可以查看注册的服务,如下编写测试代码,查看服务信息/** * 博客:https://lisen.cc * Description: * * @Author: 香草物语 * DateTime: 2021-07-17 22:11 */ @RestController public class MovieController { @Resource private DiscoveryClient discoveryClient; @Resource private RestTemplate restTemplate; @Resource private LoadBalancerClient loadBalancerClient; @GetMapping("/user-instance") public List<ServiceInstance> showInfo(){ return discoveryClient.getInstances("microservice-provider-user"); } @GetMapping("/user/{id}") public User findById(@PathVariable Long id){ return restTemplate.getForObject("http://microservice-provider-user/"+id, User.class); } @GetMapping("log/microservice-provider-user") public ServiceInstance userLog(){ return loadBalancerClient.choose("microservice-provider-user"); } }多次访问http://localhost:8081/log/microservice-provider-user,查看输出信息可以看到,服务会依次调用9001、9002端口,也就是通过轮询的方式,依次访问两个服务提供者。Ribbon自定义配置很多场景下,可以根据需要自定义Ribbon的配置,例如修改Ribbon负载均衡器规则等。Spring Cloud允许通过Java代码或属性自定义Ribbon的配置。使用代码自定义Ribbon配置使用Ribbon时一定要注意版本的问题,不然会各种报错。springcloud 2020.0.1 版本之后 删除了eureka中的ribbon,替代ribbon的是spring cloud自带的LoadBalancer,默认使用的是轮询的方式。先说一下我这边使用的组件的版本信息:Spring Boot 2.5.2Spring Cloud 2020.0.3添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>{message type="success" content="无需添加ribbon依赖,eureka-client中已包含"/}配置文件配置文件与上面配置一致,不再单独说明。server: port: 8081 eureka: instance: prefer-ip-address: false client: service-url: defaultZone: http://peer1:8761/eureka,http://peer2:8762/eureka spring: application: name: microservice-consumer-movie自定义轮询规则PeachLoadBalancer.java/** * 博客:https://lisen.cc * Description: * * @Author: 香草物语 * DateTime: 2021-07-18 11:54 */ public class PeachLoadBalancer implements ReactorServiceInstanceLoadBalancer { private static final Log log = LogFactory.getLog(PeachLoadBalancer.class); final AtomicInteger position;//请求的次数 final String serviceId; //服务名称 用于提示报错信息的 private int flag = 0; //自己定义的计数器 //两个参数的构造方法 需要服务名称和实例提供者 这个在方法中传递进来 public PeachLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) { //如果不传人请求次数就自己初始化 反正每次都+1 this(new Random().nextInt(1000), serviceId,serviceInstanceListSupplierProvider); } public PeachLoadBalancer(int seedPosition, String serviceId, ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) { this.position = new AtomicInteger(seedPosition); this.serviceId = serviceId; this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; } ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; @Override public Mono<Response<ServiceInstance>> choose(Request request) { //从服务提供者中获取到当前request请求中的serviceInstances并且遍历 ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider .getIfAvailable(NoopServiceInstanceListSupplier::new); return supplier.get(request).next() .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances)); } private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) { Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances); if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) { ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer()); } return serviceInstanceResponse; } private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) { if (instances.isEmpty()) { if (log.isWarnEnabled()) { log.warn("No servers available for service: " + serviceId); } return new EmptyResponse(); } //pos是当前请求的次数 这样可以自定义负载均衡的切换 这个每次+1的操作是复制的 最好是不删 int pos = Math.abs(this.position.incrementAndGet()); if (pos%4==0){ //是4的倍数就切换 flag += 1; } if (flag >= instances.size()){ flag = 0; } //主要的就是这句代码设置负载均衡切换 ServiceInstance instance = instances.get(flag); return new DefaultResponse(instance); } }增加轮询配置/** * 博客:https://lisen.cc * Description: * * @Author: 香草物语 * DateTime: 2021-07-18 11:21 */ public class CustomLoadBalancerConfiguration { @Bean ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new PeachLoadBalancer(loadBalancerClientFactory .getLazyProvider(name, ServiceInstanceListSupplier.class), name); } }配置轮询对应的服务/** * 博客:https://lisen.cc * Description:使用RibbonClient为特定name的Ribbon Client自定义配置 * 使用@RibbonClient的configuration,指定Ribbon的配置类 * * @Author: 香草物语 * DateTime: 2021-07-18 10:16 */ @Configuration @LoadBalancerClient(name = "microservice-provider-user",configuration = CustomLoadBalancerConfiguration.class) public class MyRibbonConfig { @Bean @LoadBalanced //基于rest+ribbon的调用方式需要加此注解 public RestTemplate restTemplate() { return new RestTemplate(); } }测试/** * 博客:https://lisen.cc * Description: * * @Author: 香草物语 * DateTime: 2021-07-17 22:11 */ @RestController public class MovieController { @Resource private DiscoveryClient discoveryClient; @Resource private RestTemplate restTemplate; @Resource private LoadBalancerClient loadBalancerClient; @GetMapping("/user-instance") public List<ServiceInstance> showInfo(){ return discoveryClient.getInstances("microservice-provider-user"); } @GetMapping("/user/{id}") public User findById(@PathVariable Long id){ return restTemplate.getForObject("http://microservice-provider-user/"+id, User.class); } @GetMapping("log/microservice-provider-user") public ServiceInstance userLog(){ return loadBalancerClient.choose("microservice-provider-user"); } }多次访问http://localhost:8081/log/microservice-provider-user,可以查看端口号,判断是否根据配置的轮询规则分别访问9001和9002端口。
2021年07月17日
1,274 阅读
0 评论
0 点赞
2021-07-17
Spring Cloud Eureka消费者获取服务者信息
在Spring Cloud Eureka配置集群中,我们介绍了Eureka集群部署的方式,这篇文章,我们继续编写服务的提供者及服务的消费者。服务提供者我们创建microservice-simple-provider-user工程,作为服务的提供者。添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>添加配置文件spring: application: name: microservice-provider-user eureka: client: service-url: default-zone: http://peer1:8761/eureka,http://peer2:8762/eureka instance: prefer-ip-address: true metadata-map: my-metadata: 自定义元数据创建用户实体User.java@Data public class User { private Long id; private String userName; private String name; private Integer age; private BigDecimal balance; }创建服务提供者@RestController public class UserController { @GetMapping("{id}") public User findById(@PathVariable Long id){ User user = new User(); user.setId(id); user.setName("zhangsan"); user.setName("张三"); user.setAge(31); user.setBalance(new BigDecimal(300)); return user; } }服务消费者我们创建microservice-consumer-movie工程,作为服务的消费者。添加依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>添加配置文件spring: application: name: microservice-provider-user eureka: client: service-url: default-zone: http://peer1:8761/eureka,http://peer2:8762/eureka instance: prefer-ip-address: true metadata-map: my-metadata: 自定义元数据我们通过metadata-map可以自定义元数据。测试获取服务提供者信息import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; /** * 博客:https://lisen.cc * Description: * * @Author: 香草物语 * DateTime: 2021-07-17 22:11 */ @RestController public class MovieController { @Resource private DiscoveryClient discoveryClient; @GetMapping("/user-instance") public List<ServiceInstance> showInfo(){ return discoveryClient.getInstances("microservice-provider-user"); } }我们访问http://localhost:8081/user-instance,可以获取到服务提供者的详细信息[{ "scheme": "http", "host": "192.168.3.48", "port": 8080, "metadata": { "management.port": "8080", "my-metadata": "自定义元数据" }, "secure": false, "uri": "http://192.168.3.48:8080", "serviceId": "MICROSERVICE-PROVIDER-USER", "instanceId": "DESKTOP-PPDVS3E:microservice-provider-user", "instanceInfo": { "instanceId": "DESKTOP-PPDVS3E:microservice-provider-user", "app": "MICROSERVICE-PROVIDER-USER", "appGroupName": null, "ipAddr": "192.168.3.48", "sid": "na", "homePageUrl": "http://192.168.3.48:8080/", "statusPageUrl": "http://192.168.3.48:8080/actuator/info", "healthCheckUrl": "http://192.168.3.48:8080/actuator/health", "secureHealthCheckUrl": null, "vipAddress": "microservice-provider-user", "secureVipAddress": "microservice-provider-user", "countryId": 1, "dataCenterInfo": { "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", "name": "MyOwn" }, "hostName": "192.168.3.48", "status": "UP", "overriddenStatus": "UNKNOWN", "leaseInfo": { "renewalIntervalInSecs": 30, "durationInSecs": 90, "registrationTimestamp": 1626531693579, "lastRenewalTimestamp": 1626531693579, "evictionTimestamp": 0, "serviceUpTimestamp": 1626531693579 }, "isCoordinatingDiscoveryServer": false, "metadata": { "management.port": "8080", "my-metadata": "自定义元数据" }, "lastUpdatedTimestamp": 1626531693579, "lastDirtyTimestamp": 1626531693019, "actionType": "ADDED", "asgName": null } }]
2021年07月17日
1,240 阅读
0 评论
0 点赞
1
...
3
4
5
...
8