使用Spring Boot和Redis实现消息队列

19-02-07 banq
                   

在本文中,我们将介绍如何通过Spring Data Redis将Redis与Spring Boot一起使用的基础知识库。

我们将构建一个应用程序,演示如何通过Web界面执行CRUD操作Redis,Github上提供了该项目的完整源代码。

Redis是一个开源的内存中键值数据存储,用作数据库,缓存和消息代理。在实现方面,Key Value存储代表NoSQL空间中最大和最老的成员之一。Redis支持数据结构,如字符串,散列,列表,集和带范围查询的有序集。

春季数据Redis的框架,可以很容易地编写,通过提供一个抽象的数据存储使用Redis的键值存储的Spring应用程序。

设置Redis服务器

可从http://redis.io/download免费获得。

如果您使用的是Mac,可以使用自制软件安装它:

brew install redis

然后启动服务器:

$ redis-server

Maven依赖

让我们在pom.xml中为我们正在构建的示例应用程序声明必要的依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Redis配置

我们需要将我们的应用程序与Redis服务器连接起来。为了建立这种连接,我们使用的是Redis客户端实现Jedis。让我们从配置bean定义开始:

@Bean
JedisConnectionFactory jedisConnectionFactory() {
    return new JedisConnectionFactory();
}
 
@Bean
public RedisTemplate<String, Object> redisTemplate() {
    final RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
    template.setConnectionFactory(jedisConnectionFactory());
    template.setValueSerializer(new GenericToStringSerializer<Object>(Object.class));
    return template;
}

JedisConnectionFactory生成Bean,所以我们可以创建一个RedisTemplate查询数据。

消息发布者

遵循SOLID原则,我们创建一个MessagePublisher接口:

public interface MessagePublisher {
 
    void publish(final String message);
}

我们实现MessagePublisher接口以使用高级RedisTemplate来发布消息,因为RedisTemplate允许任意对象作为消息传入:

@Service
public class MessagePublisherImpl implements MessagePublisher {
     
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private ChannelTopic topic;
 
    public MessagePublisherImpl() {
    }
 
    public MessagePublisherImpl(final RedisTemplate<String, Object> redisTemplate, final ChannelTopic topic) {
        this.redisTemplate = redisTemplate;
        this.topic = topic;
    }
 
    public void publish(final String message) {
        redisTemplate.convertAndSend(topic.getTopic(), message);
    }
 
}

我们还在RedisConfig中将其定义为bean :

@Bean
MessagePublisher redisPublisher() {
    return new MessagePublisherImpl(redisTemplate(), topic());
}

消息监听器

为了订阅消息,我们需要实现MessageListener接口:每次新消息到达时,都会调用回调,并通过名为onMessage的方法执行用户代码。此接口允许访问消息,通过它接收的通道以及订阅用于匹配通道的任何模式。因此,我们创建一个服务类来实现MessageSubscriber:

@Service
public class MessageSubscriber implements MessageListener {
     
    public static List<String> messageList = new ArrayList<String>();
 
    public void onMessage(final Message message, final byte[] pattern) {
        messageList.add(message.toString());
        System.out.println("Message received: " + new String(message.getBody()));
    }
 
}

我们向RedisConfig添加一个bean定义:

@Bean
MessageListenerAdapter messageListener() {
    return new MessageListenerAdapter(new MessageSubscriber());
}

RedisRepository

现在我们已经将应用程序配置为与Redis服务器交互,我们将准备应用程序以获取示例数据。

对于此示例,我们使用两个字段定义Movie模型:

private String id;
private String name;
//standard getters and setters

存储库接口:

与其他Spring Data项目不同,Spring Data Redis确实提供了在其他Spring Data接口之上构建的任何功能。对于那些有其他Spring Data项目经验的人来说是多余的。

通常,不需要使用Spring Data项目编写存储库接口的实现。我们只是简单地与界面进行交互。Spring Data JPA提供了许多存储库接口,可以扩展这些接口以获取CRUD操作,派生查询和分页等功能。

所以,遗憾的是,我们需要编写自己的接口,然后定义方法:

public interface RedisRepository {
 
    Map<Object, Object> findAllMovies();
 
    void add(Movie movie);
 
    void delete(String id);
 
    Movie findMovie(String id);
     
}

我们的实现类使用  redisTemplate  在我们的配置类中定义RedisConfig。

我们使用Spring Data Redis提供的HashOperations模板:

@Repository
public class RedisRepositoryImpl implements RedisRepository {
    private static final String KEY = "Movie";
     
    private RedisTemplate<String, Object> redisTemplate;
    private HashOperations hashOperations;
     
    @Autowired
    public RedisRepositoryImpl(RedisTemplate<String, Object> redisTemplate){
        this.redisTemplate = redisTemplate;
    }
 
    @PostConstruct
    private void init(){
        hashOperations = redisTemplate.opsForHash();
    }
     
    public void add(final Movie movie) {
        hashOperations.put(KEY, movie.getId(), movie.getName());
    }
 
    public void delete(final String id) {
        hashOperations.delete(KEY, id);
    }
     
    public Movie findMovie(final String id){
        return (Movie) hashOperations.get(KEY, id);
    }
     
    public Map<Object, Object> findAllMovies(){
        return hashOperations.entries(KEY);
    }
 
   
}

我们来看一下  init() 方法。在此方法中,我们使用名为opsForHash()的函数,该函数  返回对绑定到给定键的哈希值执行的操作。然后,我们使用在init()中定义的  hashOps进行所有CRUD操作。

Web界面

我们将讨论将Redis CRUD操作功能添加到Web界面。在我们的网页中添加电影。Key是Movie ID,Value是实际对象。但是,我们稍后会解决此问题,因此只有电影名称显示为值。

因此,我们将一个表单添加到HTML文档并分配适当的名称和ID:

<form id="addForm">
<div class="form-group">
                    <label for="keyInput">Movie ID (key)</label>
                    <input name="keyInput" id="keyInput" class="form-control"/>
                </div>
<div class="form-group">
                    <label for="valueInput">Movie Name (field of Movie object value)</label>
                    <input name="valueInput" id="valueInput" class="form-control"/>
                </div>
                <button class="btn btn-default" id="addButton">Add</button>
            </form>

现在我们使用JavaScript来保存表单提交中的值:

$(document).ready(function() {
    var keyInput = $('#keyInput'),
        valueInput = $('#valueInput');
 
    refreshTable();
    $('#addForm').on('submit', function(event) {
        var data = {
            key: keyInput.val(),
            value: valueInput.val()
        };
 
        $.post('/add', data, function() {
            refreshTable();
            keyInput.val('');
            valueInput.val('');
            keyInput.focus();
        });
        event.preventDefault();
    });
 
    keyInput.focus();
});

我们为POST请求分配@RequestMapping值,请求键和值,创建一个Movie对象,并将其保存到存储库:

@RequestMapping(value = "/add", method = RequestMethod.POST)
public ResponseEntity<String> add(
    @RequestParam String key,
    @RequestParam String value) {
     
    Movie movie = new Movie(key, value);
     
    redisRepository.add(movie);
    return new ResponseEntity<>(HttpStatus.OK);
}

查看内容:

一旦电影 对象添加,我们刷新表中显示更新的表。在7.1节的JavaScript代码块中,我们调用了一个名为refreshTable()的JavaScript函数  。此函数执行GET请求以检索存储库中的当前数据:

function refreshTable() {
    $.get('/values', function(data) {
        var attr,
            mainTable = $('#mainTable tbody');
        mainTable.empty();
        for (attr in data) {
            if (data.hasOwnProperty(attr)) {
                mainTable.append(row(attr, data[attr]));
            }
        }
    });
}

GET请求由名为findAll()的方法处理,该方法检索存储在存储库中的所有Movie对象,然后将数据类型从Map <Object,Object>转换为Map <String,String>:

@RequestMapping("/values")
public @ResponseBody Map<String, String> findAll() {
    Map<Object, Object> aa = redisRepository.findAllMovies();
    Map<String, String> map = new HashMap<String, String>();
    for(Map.Entry<Object, Object> entry : aa.entrySet()){
        String key = (String) entry.getKey();
        map.put(key, aa.get(key).toString());
    }
    return map;
}

删除:

我们编写Javascript来执行POST请求/删除,刷新表,并将键盘焦点设置为键输入: 

function deleteKey(key) {
    $.post('/delete', {key: key}, function() {
        refreshTable();
        $('#keyInput').focus();
    });
}

我们请求Key并根据此Key删除redisRepository中 的对象:

@RequestMapping(value = "/delete", method = RequestMethod.POST)
public ResponseEntity<String> delete(@RequestParam String key) {
    redisRepository.delete(key);
    return new ResponseEntity<>(HttpStatus.OK);
}

结论

在本教程中,我们介绍了Spring Data Redis以及将其连接到Web应用程序以执行CRUD操作的一种方法。

示例应用程序的源代码在Github上