教你如何在Spring Boot中使用RSocket

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了教你如何在Spring Boot中使用RSocket相关的知识,希望对你有一定的参考价值。

参考技术A

RSocket应用层协议支持 Reactive Streams语义, 例如:用RSocket作为HTTP的一种替代方案。在本教程中, 我们将看到RSocket用在spring boot中,特别是spring boot 如何帮助抽象出更低级别的RSocket API。

让我们从添加spring-boot-starter-rsocket依赖开始:

这个依赖会传递性的拉取RSocket相关的依赖,比如:rsocket-core 和 rsocket-transport-netty

现在继续我们的简单应用程序。为了突出RSocket提供的交互模式,我打算创建一个交易应用程序, 交易应用程序包括客户端和服务器。

3.1. 服务器设置

首先,我们设置由springboot应用程序引导的RSocket server服务器。 因为有spring-boot-starter-rsocket dependency依赖,所以springboot会自动配置RSocket server。 跟平常一样, 可以用属性驱动的方式修改RSocket server默认配置值。例如:通过增加如下配置在application.properties中,来修改RSocket端口

也可以根据需要进一步修改服务器的其他属性

3.2.设置客户端

接下来,我们来设置客户端,也是一个springboot应用程序。虽然springboot自动配置大部分RSocket相关的组件,但还要自定义一些对象来完成设置。

这儿我们正在创建RSocket客户端并且配置TCP端口为:7000。注意: 该服务端口我们在前面已经配置过。 接下来我们定义了一个RSocket的装饰器对象RSocketRequester。 这个对象在我们跟RSocket server交互时会为我们提供帮助。 定义这些对象配置后,我们还只是有了一个骨架。在接下来,我们将暴露不同的交互模式, 并看看springboot在这个地方提供帮助的。

我们从Request/Response开始,HTTP也使用这种通信方式,这也是最常见的、最相似的交互模式。 在这种交互模式里, 由客户端初始化通信并发送一个请求。之后,服务器端执行操作并返回一个响应给客户端--这时通信完成。 在我们的交易应用程序里, 一个客户端询问一个给定的股票的当前的市场数据。 作为回复,服务器会传递请求的数据。

4.1.服务器

在服务器这边,我们首先应该创建一个controller 来持有我们的处理器方法。 我们会使用 @MessageMapping注解来代替像SpringMVC中的@RequestMapping或者@GetMapping注解

来研究下我们的控制器。 我们将使用@Controller注解来定义一个控制器来处理进入RSocket的请求。 另外,注解@MessageMapping让我们定义我们感兴趣的路由和如何响应一个请求。 在这个示例中, 服务器监听路由currentMarketData, 并响应一个单一的结果Mono<MarketData>给客户端。

4.2. 客户端

接下来, 我们的RSocket客户端应该询问一只股票的价格并得到一个单一的响应。 为了初始化请求, 我们该使用RSocketRequester类,如下:

注意:在示例中,RSocket客户端也是一个REST风格的controller,以此来访问我们的RSocket服务器。因此,我们使用@RestController和@GetMapping注解来定义我们的请求/响应端点。 在端点方法中, 我们使用的是类RSocketRequester并指定了路由。 事实上,这个是服务器端RSocket所期望的路由,然后我们传递请求数据。最后,当调用retrieveMono()方法时,springboot会帮我们初始化一个请求/响应交互。

接下来我们将查看 Fire And Forget交互模式。正如名字提示的一样,客户端发送一个请求给服务器,但是不期望服务器的返回响应回来。 在我们的交易程序中, 一些客户端会作为数据资源服务,并且推送市场数据给服务器端。

5.1.服务器端

我们来创建另外一个端点在我们的服务器应用程序中,如下:

我们又一次定义了一个新的@MessageMapping路由为collectMarketData。此外, Spring Boot自动转换传入的负载为一个MarketData实例。 但是,这儿最大的不同是我们返回一个Mono<Void>,因为客户端不需要服务器的返回。

5.2. 客户端

来看看我们如何初始化我们的fire-and-forget模式的请求。 我们将创建另外一个REST风格的端点,如下:

这儿我们指定路由和负载将是一个MarketData实例。 由于我们使用send()方法来代替retrieveMono(),所有交互模式变成了fire-and-forget模式。

请求流是一种更复杂的交互模式, 这个模式中客户端发送一个请求,但是在一段时间内从服务器端获取到多个响应。 为了模拟这种交互模式, 客户端会询问给定股票的所有市场数据。

6.1.服务器端

我们从服务器端开始。 我们将添加另外一个消息映射方法,如下:

正如所见, 这个处理器方法跟其他的处理器方法非常类似。 不同的部分是我们返回一个Flux<MarketData>来代替Mono<MarketData>。 最后我们的RSocket服务器会返回多个响应给客户端。

6.2.客户端

在客户端这边, 我们该创建一个端点来初始化请求/响应通信,如下:

我们来研究下RSocket请求。 首先我们定义了路由和请求负载。 然后,我们定义了使用retrieveFlux()调用的响应期望。这部分决定了交互模式。 另外注意:由于我们的客户端也是REST风格的服务器,客户端也定义了响应媒介类型MediaType.TEXT_EVENT_STREAM_VALUE。

这里我们给异常处理方法标记注解为@MessageExceptionHandler。作为结果, 这个方法将处理所有类型的异常, 因为Exception是所有其他类型的异常的超类。 我们也可以明确地创建更多的不同类型的,不同的异常处理方法。 这当然是请求/响应模式,并且我们返回的是Mono<MarketData>。我们期望这里的响应类型跟我们的交互模式的返回类型相匹配。

大聪明教你学Java | Spring Boot 使用自定义注解实现操作日志的记录

前言

我们无论开发什么应用,其中都会有一个功能需求——记录操作日志,有了操作日志的记录既保证应用的完成性,也可以在因为误操作而出现系统崩溃的情况下通过操作日志进行溯源,可以说记录操作日志的功能在任何一款应用软件中都是不可或缺的。那么各位小伙伴可以想一下,如果我们要实现记录操作日志的功能,我们该怎么实现呢?

最简单粗暴的办法就是在每一个方法里增加一行代码来记录本次操作(插入操作日志表,本质就是一条 insert 语句),这样虽然可以实现我们所需要的功能,但是实现过程就比较麻烦了,而且容易出错误。这时候如果使用自定义注解的话就会方便很多,很大程度上简化了我们的代码,而且让代码可读性更强。👍

那么今天就和大家分享一下如何利用自定义注解实现“记录操作日志”的功能🤞

利用自定义注解实现操作日志的记录

实现自定义注解

我们还是先引入 Maven 依赖👇

<!-- SpringBoot 拦截器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

接下来我们实现一个注解类(注解在 Java 中与类、接口的声明类似,只是所使用的关键字有所不同,声明注解使用 @interface 关键字。在底层实现上,所有定义的注解都会自动继承 java.lang.annotation.Annotation 接口。)

import java.lang.annotation.*;

/**
 * 自定义注解-实现操作日志记录
 * 此处代码仅作参考
 * 实际开发过程中根据自己的业务功能添加所需要的字段
 * 
 * @description: Log
 * @author: 庄霸.liziye
 * @create: 2021-12-21 12:47
 **/
@Target( ElementType.PARAMETER, ElementType.METHOD )
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log

    /**
     * 模块 
     */
    public String title() default "";

    /**
     * 操作类型
     * 
     * 此处的 BusinessType 是我自己定义的枚举类
     * BusinessType.OTHER 代表其他操作
     */
    public BusinessType businessType() default BusinessType.OTHER;
    

关于注解的定义,咱们多说几句😁
① 访问修饰符必须为 public,不写的话则默认为 public。
② 自定义注解中元素的类型只能是基本数据类型、String、Class、枚举类型,也可以是注解类型(体现了注解的嵌套效果)以及上述类型的一位数组。
③ 自定义注解中的 default 代表默认值。
④ 自定义注解上的 @Target 注解的作用是限定自定义注解能够被应用在哪些Java 元素上。ElementType 是一个枚举类型,其源码如下👇

public enum ElementType 
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE

⑤ 自定义注解上的 @Retention 注解用来定义其声明周期。注解的生命周期有三个阶段,分别是:源文件阶段;编译阶段;运行阶段。RetentionPolicy 同样是一个枚举类型,其源码如下👇

ublic enum RetentionPolicy 
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME

⑥ 自定义注解上的 @Documented 是一个标记注解,表明该自定义注解应该被 JavaDoc 工具记录.。默认情况下 JavaDoc 的记录是不包括注解的, 但如果声明注解时指定了 @Documented ,该注解就会被 JavaDoc 之类的工具处理,所以注解类型信息也会被生成到 JavaDoc 文档中。

下面我们就需要定义一个切面类,来进一步的实现自定义注解的功能,代码如下👇(这里就只贴一下重要代码)

/**
 * 自定义注解-操作日志记录处理
 * 此处代码仅作参考,开发过程中根据自己的业务功能需求添加所需要业务逻辑
 *
 * @description: Log
 * @author: 庄霸.liziye
 * @create: 2021-12-22 13:07
 **/
@Aspect
@Component
public class LogAspect

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    // 配置织入点-xxx代表自定义注解的存放位置,如:com.test.annotation.Log
    @Pointcut("@annotation(xxxx)")
    public void logPointCut()
    
    

    /**
     * 处理完请求后执行此处代码
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
    
        handleLog(joinPoint, controllerLog, null, jsonResult);
    

    /**
     * 如果处理请求时出现异常,在抛出异常后执行此处代码
     * 
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
    
        handleLog(joinPoint, controllerLog, e, null);
    

	/**
     * 日志处理
     */
    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
    
        try
        
            //此处为处理日志的具体业务逻辑
            //最终将操作信息插入至操作日志表
            //使用自定义注解的时候只需要将注解写在对应方法上即可(记得给自定义注解中的属性赋值哦),博客最后有代码截图~
            //获取自定义注解赋值的方式也很简单,以上述自定义注解类代码为例,
            //获取 title 时直接使用 controllerLog.title() 即可,
            //获取枚举类型的 businessType 时,使用controllerLog.businessType().ordinal(),需要注意的是 ordinal() 方法返回的是枚举对象的序号。
        
        catch (Exception exp)
        
            // 记录本地异常日志
            log.error("**** 出现异常 ****");
            log.error("异常信息:", exp.getMessage());
            exp.printStackTrace();
        
    

这里咱们主要解释一下切面类中的各个注解的作用:
① @Aspect:一个标识性的注解,表示当前类是一个切面,可以被容器读取。
② @Component:表示该类被 Spring 管理,可以理解为将该类注入进 Spring(就像向 Spring 注入 bean 一样)。
③ @Pointcut:Pointcut是植入切面的触发条件。每个 Pointcut 的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public 及 void。可以将Pointcut 中的方法看作是一个被切面引用的标记符号,因为表达式不直观,因此我们可以通过方法签名的方式为此表达式命名。因此 Pointcut 中的方法只需要方法签名,而不需要在方法体内编写实际代码。
④ @AfterReturning:后置增强,相当于AfterReturningAdvice,给方法增加该注解后表示当方法正常退出时执行此方法。
⑤ @AfterThrowing:异常抛出增强,相当于ThrowsAdvice,给方法增加该注解后表示当方法抛出异常时执行此方法。
⑥ pointcut/value 这两个属性的作用是一样的,它们都属于指定切入点对应的切入表达式。一样既可以是已有的切入点,也可直接定义切入点表达式。当指定了 pointcut 属性值后,value 属性值将会被覆盖。
⑦ returning:该属性指定一个形参名,用于表示方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值。需要注意的是,在方法中定义该形参时指定了类型,则限制目标方法必须返回此类型的值或没有返回值。

至此,我们的自定义注解就配置完成了,最后我们再来看一下如何使用自定义注解~

使用自定义注解

使用自定义注解就很简单了,使用方式和其他注解是相同的👇

只要我们在需要记录操作日志的方法上增加配置好的自定义注解(别忘了给自定义注解中的字段赋值哦~)就可以使用啦,我们每次请求方法时,都会帮助我们记录本次操作。

P.S. 虽然定义自定义注解的过程麻烦了一些,但是磨刀不误砍柴工,合理的使用自定义注解不仅仅提升了编码效率,而且可以瞬间提升代码的逼格💪

小结

本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨🙇‍

希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●’◡’●)

如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。

你在被打击时,记起你的珍贵,抵抗恶意;
你在迷茫时,坚信你的珍贵,抛开蜚语;
爱你所爱 行你所行 听从你心 无问东西

以上是关于教你如何在Spring Boot中使用RSocket的主要内容,如果未能解决你的问题,请参考以下文章

10年Java老兵教你如何让Spring Boot 的配置 “动” 起来?(建议收藏)

手把手教你基于Retrofit实现自己的轻量级http调用工具

手把手教你手写一个最简单的 Spring Boot Starter

Spring Boot性能太差,教你几招轻松搞定

大聪明教你学Java | Spring Boot 使用自定义注解实现操作日志的记录

Spring boot-手把手教你使用Token