Spring Cloud实现自定义注解发送消息到 RabbitMQ

自定义注解

第一步:定义注解

代码

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotifyAnnotation {
/**
     * 这里注解属性用的是枚举类型,也可以是其它类型,如String
     *     String value(); 
     * value是默认属性名称,如果自定义注解中只有一个名为 value 的属性时,
     * 可以直接用 @NotifyAnnotation("参数"),否则需要 @NotifyAnnotation(属性名="参数")
     * 
     * 还可以使用 defalt 关键字设定默认值,
     *     String value() default "默认参数";
     * 此时可以使用注解不传参 @NotifyAnnotation
     */
    /**
     * 行为类型
     */
    OperateTypeEnum[] operationType();

    /**
     * 通知种类
     */
    NoticeTypeEnum[] noticeType();

注解含义
@interface:表示该类是注解类型。

@Documented:属于标志注解,添加这个注解会被 javadoc工具记录,在生成javadoc的时候就会把@Documented注解给显示出来,正常情况下javadoc中不包含注解的。

@Target(ElementType.METHOD):用于设定注解使用范围

ElementType的用法

METHOD:可用于方法上
TYPE:可用于类或者接口上
ANNOTATION_TYPE:可用于注解类型上(被@interface修饰的类型)
CONSTRUCTOR:可用于构造方法上
FIELD:可用于域上
LOCAL_VARIABLE:可用于局部变量上
PACKAGE:用于记录java文件的package信息
PARAMETER:可用于参数上

@Retention(RetentionPolicy.RUNTIME):用于定义注解的生命周期

RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

这3个生命周期分别对应于:Java源文件(.java文件) —> .class文件 —> 内存中的字节码。

那怎么来选择合适的注解生命周期呢?

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。

注解中的枚举类属性

/**
 * 行为类型
 *
 * @author MrTan
 */

public enum OperateTypeEnum {
    /**
     * 新增操作
     */
    ADD("01", "新增"),
    /**
     * 删除操作
     */
    DEL("02", "删除"),
    /**
     * 修改操作
     */
    MOD("03", "修改");
    /**
     * 编号
     */
    private final String code;
    /**
     * 名称
     */
    private final String name;

    OperateTypeEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }

    public String getCode() {
        return code;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "OperateTypeEnum{" +
                "code='" + code + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

用法如下,在方法上添加注解
在这里插入图片描述

第二步:定义注解的AOP(切面逻辑)类

@Aspect //把当前类标识为一个切面,供容器读取。
@Component //官方的原话是:带此注解的类看为组件,当使用基于注解的配置和类路径扫描的时候,这些类就会被实例化。通俗的讲就是标志将该类的实例交给Spring容器管理,随着程序的运行而运行,不需要自己new
public class NotifyAspect {
	//异步发送功能的Service
    private final NoticeAsyncService noticeAsyncService;
	//通过构造方法给本类中的Service赋值
    public NotifyAspect(NoticeAsyncService noticeAsyncService) {
        this.noticeAsyncService = noticeAsyncService;
    }

/**
21      * @Around:环绕通知
22      * 属性:value 切入点表达式
23      * 位置 在方法的定义前后执行
24      *
25      * 特点:
26      * 1. 他是功能最强的通知
27      * 2. 在目标方法的前后都能增强功能
28      * 3. 控制目标方法是否被调用执行
29      * 4. 修改原来的目标方法的执行结果,影响最后的调用结果
30      * 环绕通知等同于JDK动态代理的InvocationHandler接口
31      *
32      * 参数:ProceedingJoinPoint 就等同于Method,作用:执行目标方法的
33      * 返回值:就是目标方法的执行结果,可以被修改。
34      *
35      * 环绕通知:经常做事务,在目标方法之前开启事务,执行目标方法,目标方法执行完成后结束事务
36      */
    @Around("@annotation(notifyAnnotation)")
    public Object doAround(ProceedingJoinPoint joinPoint, NotifyAnnotation notifyAnnotation) throws Throwable {
        //获取注解的属性值
        OperateTypeEnum[] operationType = notifyAnnotation.operationType();
        NoticeTypeEnum[] noticeType = notifyAnnotation.noticeType();
        //获取方法的参数
        Object[] args = joinPoint.getArgs();
        //ObjectMapper 的 convertValue方法可以将数组转换成指定对象
        ObjectMapper objectMapper = new ObjectMapper();
		//消息通知封装类
        SysNotice notice = new SysNotice();
        List<Map<String, String>> data = new ArrayList();

        //编号以1结尾代表是角色变动,否则是用户变动
        if (noticeType[0].getCode().endsWith("1")) {
            RolegroupsRoleListDto rolegroupsRoles = objectMapper.convertValue(args[0], RolegroupsRoleListDto.class);
            operation.setRelationId(rolegroupsRoles.getRoleGroupsId());
            for (String roleId : rolegroupsRoles.getRoleIds()) {
                Map<String, String> map = new HashMap<>();
                map.put("roleId", roleId);
                data.add(map);
            }
        } else {
            UsergroupUserListDto usergroupsUserList = objectMapper.convertValue(args[0], UsergroupUserListDto.class);
            operation.setRelationId(usergroupsUserList.getUserGroupsId());
            for (String userId : usergroupsUserList.getUserIds()) {
                Map<String, String> map = new HashMap<>();
                map.put("userId", userId);
                data.add(map);
            }
        }
        notice.setOperationMessage(data);
        notice.setNoticeType(noticeType[0]);
        notice.setId(UUID.randomUUID().toString());

        Object result;
        try {
        	//执行切入点(被注解)的方法,得到执行结果
            result = joinPoint.proceed();
        } catch (Exception var6) {
            notice.setResult(var6.getMessage());
            this.noticeAsyncService.send(notice);
            throw var6;
        }
		//被注解的方法执行成功时记录执行结果和时间,并将消息异步发送到MQ
		//如果执行不成功则在try catch中捕捉异常并记录,然后继续发送消息到MQ
        notice.setResult(result);
        notice.setEndTime(LocalDateTime.now());
        //调用异步发送消息方法
        this.noticeAsyncService.send(notice);
        return result;
    }
}

关于 ProceedingJoinPoint 和 JointPoint 的区别和使用方法推荐阅读这篇博文SpringAOP中的JointPoint和ProceedingJoinPoint使用详解(附带详细示例)

第三步:定义异步发送消息类

需要在启动类上添加@EnableAsync开启异步功能

/**
 * 异步发送
 * @author MrTan
 */
@Service
public class NoticeAsyncService {
    private final StreamBridge streamBridge;

    public NoticeAsyncService(StreamBridge streamBridge) {
        this.streamBridge = streamBridge;
    }

     @Async //异步发送注解
    //将消息发送到指定的交换机
    public void send(SysNotice notice) {
        this.streamBridge.send("source-out-1", notice);
    }
}

添加RabbitMQ

SpringCloud 的 stream 包中包含对消息中间件的支持,只需要简单的配置就能在服务中使用 rabbit 详情可浏览:官方文档

第一步:添加依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

第二步:添加配置

spring:

#创建链接
  rabbitmq:
    #地址,默认本机5672端口
    addresses: localhost:5672
    #用户名和密码 默认 guest
    username: guest
    password: guest
    #虚拟机节点 / 表示根节点,这里的 blockchain 表示在根节点下的 blockchain 分支,通常生产环境中会根据项目发布在各分支方便隔离区分和管理 
    virtual-host: blockchain
    
#定义信通和交换机,
#生产者发布的消息由交换机接收,交换机需要在信通中创建(交换机不具备存储的能力)
  cloud:
    stream:
      bindings:
        # 定义信通,命名格式 类型(direct/topic/fanout/headers)- 角色(生产者 out/消费者 in) - 序号(从0开始)
        topic-out-0:
          # 绑定交换机(已存在交换机会绑定,不存在会生成交换机并绑定)
          destination: authmanager
        topic-out-1:
          destination: authmanager_group

RabbitMQ 的消息模式(交换机类型)

direct 路由模式:通过 routing-key 匹配指定队列进行消息的消费

topic 主题模式:模糊的 routing-key 匹配,可以用 # 匹配所有,用 * 匹配一个

 如:student.zhangsan.27             
 匹配:                                      不匹配:
      #                                           *
      student.#                                   student.*
      #.27                                        *.27
      student.*.27                            

fanout 发布订阅模式:是一种广播机制,它没有路由key,所有绑定交换机的队列都会收到消息。
heders 参数模式:通过携带的参数进行匹配

第三步:启动服务访问 rabbit 图形化界面

找到我们定义的交换机,单机可以查看交换机的信息。

在这里插入图片描述

单击我们的消息队列查看详细信息
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
至此成功完成了自定义注解在方法调用时,异步发送消息到 RabbitMQ 进行通知.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值