RabbitMQ整合Spring AMQP(三)

消息監(jiān)聽適配器

  • MessageListenerAdapter蓝撇,即消息監(jiān)聽適配器
  • 前面我們介紹了通過實現(xiàn)ChannelAwareMessageListener接口并通過onMessage方法來接收消息。
  • 除了這種方式我們也可以使用適配器對不同類型的消息進行適配處理陈莽。
1.定義消息處理類

委托類可以自己隨意定義渤昌,但是這里的方法名和參數(shù)是固定的:handleMessage

public class MessageDelegate {

    public void handleMessage(byte[] messageBody) {
        System.err.println("默認(rèn)方法, 消息內(nèi)容:" + new String(messageBody));
    }
}

可以通過查看MessageListenerAdapter類的源碼得知

2.設(shè)置消息監(jiān)聽器

這里將消息監(jiān)聽器設(shè)置為一個適配器對象,適配器需要一個委托對象走搁。

    @Bean
    public SimpleMessageListenerContainer messageContainer(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        //省略其他配置....
        
        //設(shè)置消息監(jiān)聽器
        /*container.setMessageListener(new ChannelAwareMessageListener() {
            @Override
            public void onMessage(Message message, Channel channel) throws Exception {
                String msg = new String(message.getBody());
                System.err.println("----------消費者: " + msg);
            }
        });*/
        //適配器独柑,傳遞的參數(shù)為委托者對象
        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
        container.setMessageListener(adapter);
        return container;
    }
3.運行說明

運行之前編寫的測試方法進行消息發(fā)送

    @Test
    public void testSendMessage() throws Exception {
        //1 創(chuàng)建消息
        MessageProperties messageProperties = new MessageProperties();
        //設(shè)置消息屬性
        messageProperties.getHeaders().put("desc", "信息描述..");
        messageProperties.getHeaders().put("type", "自定義消息類型..");
        Message message = new Message("Hello RabbitMQ".getBytes(), messageProperties);
        
        //發(fā)送消息
        //參數(shù):exchange, routingKey, message, messagePostProcessor
        rabbitTemplate.convertAndSend("topic001", "spring.amqp", message, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                System.err.println("------添加額外的設(shè)置---------");
                message.getMessageProperties().getHeaders().put("desc", "額外修改的信息描述");
                message.getMessageProperties().getHeaders().put("attr", "額外新加的屬性");
                return message;
            }
        });
    }

控制臺打印了如下內(nèi)容,說明通過適配器同樣可以接收到消息

------添加額外的設(shè)置---------
默認(rèn)方法, 消息內(nèi)容:Hello RabbitMQ
消息監(jiān)聽適配器相關(guān)設(shè)置
1.修改默認(rèn)監(jiān)聽方法

設(shè)置方法:adapter.setDefaultListenerMethod("consumeMessage");
MessageDelegate類中添加方法

    public void consumeMessage(byte[] messageBody) {
        System.err.println("字節(jié)數(shù)組方法, 消息內(nèi)容:" + new String(messageBody));
    }

再次運行測試方法私植,打印了如下內(nèi)容忌栅,說明方法名修改是生效的。

------添加額外的設(shè)置---------
字節(jié)數(shù)組方法, 消息內(nèi)容:Hello RabbitMQ
2.修改方法參數(shù)類型

修改為String類型:adapter.setMessageConverter(new TextMessageConverter());
自定義轉(zhuǎn)換器曲稼,實現(xiàn)MessageConverter接口

public class TextMessageConverter implements MessageConverter {

    //將一個java對象轉(zhuǎn)換為Message
    @Override
    public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
        System.err.println("toMessage");
        return new Message(object.toString().getBytes(), messageProperties);
    }

    //將Message轉(zhuǎn)換為Java對象
    @Override
    public Object fromMessage(Message message) throws MessageConversionException {
        System.err.println("fromMessage");
        //含有"text"的contentType索绪,轉(zhuǎn)換為字符串對象
        String contentType = message.getMessageProperties().getContentType();
        if(null != contentType && contentType.contains("text")) {
            return new String(message.getBody());
        }
        return message.getBody();
    }
}

修改委托類的監(jiān)聽方法參數(shù)

    public void consumeMessage(String messageBody) {
        System.err.println("字符串方法, 消息內(nèi)容:" + messageBody);
    }

添加測試方法,指定消息的contentType屬性

    @Test
    public void testSendMessage4Text() throws Exception {
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("text/plain");
        Message message = new Message("Test SpringAMQP Message".getBytes(), messageProperties);
        rabbitTemplate.send("topic001", "spring.abc", message);
        rabbitTemplate.send("topic002", "rabbit.abc", message);
    }

運行測試方法贫悄,打印了如下內(nèi)容瑞驱,說明方法參數(shù)類型修改是OK的。

fromMessage
字符串方法, 消息內(nèi)容:Test SpringAMQP Message
fromMessage
字符串方法, 消息內(nèi)容:Test SpringAMQP Message
3.隊列名與方法名匹配

修改MessageListenerAdapter的配置窄坦,采用隊列與方法名匹配方式唤反,此時只有不匹配的隊列消息才會走默認(rèn)的監(jiān)聽方法。

    @Bean
    public SimpleMessageListenerContainer messageContainer(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        //省略其他配置....
        /**
         * 2.適配器方式: 我們的隊列名稱 和 方法名稱 也可以進行一一的匹配
         */
        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
        adapter.setMessageConverter(new TextMessageConverter());
        Map<String, String> queueOrTagToMethodName = new HashMap<>();
        queueOrTagToMethodName.put("queue001", "method1");
        queueOrTagToMethodName.put("queue002", "method2");
        //設(shè)置隊列與方法名對應(yīng)
        adapter.setQueueOrTagToMethodName(queueOrTagToMethodName);
        container.setMessageListener(adapter); 
        
        return container;
    }

MessageDelegate類添加消息監(jiān)聽方法

    public void method1(String messageBody) {
        System.err.println("method1 收到消息內(nèi)容:" + new String(messageBody));
    }
    
    public void method2(String messageBody) {
        System.err.println("method2 收到消息內(nèi)容:" + new String(messageBody));
    }

運行測試方法:testSendMessage4Text鸭津,控制臺打印了如下內(nèi)容

fromMessage
method1 收到消息內(nèi)容:Test SpringAMQP Message
fromMessage
method2 收到消息內(nèi)容:Test SpringAMQP Message
MessageListenerAdapter總結(jié)
  • MessageListenerAdapter:即消息監(jiān)聽適配器
  • 通過messageListenerAdapter的代碼我們可以看出如下核心屬性:
    defaultListenerMethod: 默認(rèn)監(jiān)聽方法名稱:用于設(shè)置監(jiān)聽方法名稱
    Delegate: 實際真實的委托對象拴袭,用于處理消息
    queueOrTagToMethodName: 隊列和方法名稱綁定,即指定隊列里的消息會被綁定的方法所接受處理

消息轉(zhuǎn)換器 - MessageConverter

  • 我們在進行發(fā)送消息的時候曙博,正常情況下消息體為二進制的數(shù)據(jù)方式進行傳輸,如果希望內(nèi)部幫我們進行轉(zhuǎn)換怜瞒,或者指定自定義的轉(zhuǎn)換器父泳,就需要用到MessageConverter
  • 自定義轉(zhuǎn)換器通常是實現(xiàn)MessageConverter接口,重寫下面兩個方法:
    toMessage: java對象轉(zhuǎn)換為Message
    fromMessage: Message對象轉(zhuǎn)換為java對象
  • 3.常用轉(zhuǎn)換器
    Jackson2JsonMessageConverter: Json轉(zhuǎn)換器吴汪,可以進行java對象的轉(zhuǎn)換功能
    DefaultJackson2JavaTypeMapper: 映射器惠窄,可以進行java對象的映射關(guān)系
    自定義二進制轉(zhuǎn)換器: 比如圖片類型、PDF漾橙、 PPT杆融、 流媒體
JSON轉(zhuǎn)換器的使用
創(chuàng)建實體類
@Data
public class Order {
    private String id;
    private String name;
    private String content;
}
@Data
public class Packaged {
    private String packageId;
    private String packageName;
    private String description;
}
配置轉(zhuǎn)換器

在RabbitMQConfig配置類中配置MessageListenerAdapter對應(yīng)的轉(zhuǎn)換器

    @Bean
    public SimpleMessageListenerContainer messageContainer(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        //省略其他配置...
        /**
         * 1.1 支持json格式的轉(zhuǎn)換器
         */
        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
        adapter.setDefaultListenerMethod("consumeMessage");
        //使用json轉(zhuǎn)換器
        Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
        adapter.setMessageConverter(jackson2JsonMessageConverter);
        container.setMessageListener(adapter);
        return container;
    }
委托類中添加監(jiān)聽方法

在MessageDelegate中添加JSON格式的消息監(jiān)聽方法,對應(yīng)的參數(shù)類型是Map

public class MessageDelegate {
    public void consumeMessage(Map messageBody) {
        System.err.println("map方法, 消息內(nèi)容:" + messageBody);
    }
}
測試json轉(zhuǎn)換器

編寫測試方法霜运,注意設(shè)置消息的contentType屬性

    @Test
    public void testSendJsonMessage() throws Exception {
        Order order = new Order();
        order.setId("001");
        order.setName("消息訂單");
        order.setContent("描述信息");
        //使用jackson進行json序列化
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(order);
        System.err.println("order 4 json: " + json);
        //發(fā)送消息
        MessageProperties messageProperties = new MessageProperties();
        //這里注意一定要修改contentType為 application/json
        messageProperties.setContentType("application/json");
        Message message = new Message(json.getBytes(), messageProperties);
        rabbitTemplate.send("topic001", "spring.order", message);
    }

運行測試方法脾歇,控制臺打印內(nèi)容如下

order 4 json: {"id":"001","name":"消息訂單","content":"描述信息"}
map方法, 消息內(nèi)容:{id=001, name=消息訂單, content=描述信息}
對象映射器的使用
配置轉(zhuǎn)換器

在RabbitMQConfig配置類中配置MessageListenerAdapter對應(yīng)的轉(zhuǎn)換器蒋腮,其實依然是使用JSON轉(zhuǎn)換器,只不過需要另外對JSON轉(zhuǎn)換器設(shè)置一個對象映射器藕各,這樣接收消息時就能使用對象接收了池摧。
方法:jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);

    MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
    adapter.setDefaultListenerMethod("consumeMessage");
    Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
    //設(shè)置json與java對象的映射器
    DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
    jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
        
    adapter.setMessageConverter(jackson2JsonMessageConverter);
    container.setMessageListener(adapter);
委托類中添加監(jiān)聽方法

在MessageDelegate中添加消息監(jiān)聽方法,對應(yīng)的參數(shù)類型是Java對象

    public void consumeMessage(Order order) {
        System.err.println("order對象, 消息內(nèi)容, id: " + order.getId() + 
                ", name: " + order.getName() + 
                ", content: "+ order.getContent());
    }
測試映射器

編寫測試方法激况,注意設(shè)置消息的java對象類型: messageProperties.getHeaders().put("__TypeId__", "xxx");

    @Test
    public void testSendJavaMessage() throws Exception {
        
        Order order = new Order("002","訂單消息","訂單描述信息");
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(order);
        System.err.println("order 4 json: " + json);
        
        MessageProperties messageProperties = new MessageProperties();
        //這里注意一定要修改contentType為 application/json
        messageProperties.setContentType("application/json");
        //設(shè)置java類型:key是固定的
        messageProperties.getHeaders().put("__TypeId__", "com.rxy.spring.entity.Order");
        Message message = new Message(json.getBytes(), messageProperties);
        
        rabbitTemplate.send("topic001", "spring.order", message);
    }

運行測試方法作彤,控制臺打印內(nèi)容如下

order 4 json: {"id":"002","name":"訂單消息","content":"訂單描述信息"}
order對象, 消息內(nèi)容, id: 002, name: 訂單消息, content: 訂單描述信息
Java對象多映射轉(zhuǎn)換
配置轉(zhuǎn)換器

方法: javaTypeMapper.setIdClassMapping(idClassMapping);
可以配置多個Java對象的映射關(guān)系,從而根據(jù)對象標(biāo)識將json數(shù)據(jù)轉(zhuǎn)換為不同的Java對象

    MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
    adapter.setDefaultListenerMethod("consumeMessage");
    Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
    DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
    //配置類標(biāo)識與java類的映射關(guān)系
    Map<String, Class<?>> idClassMapping = new HashMap<String, Class<?>>();
    idClassMapping.put("order", com.rxy.spring.entity.Order.class);
    idClassMapping.put("packaged", com.rxy.spring.entity.Packaged.class);
    javaTypeMapper.setIdClassMapping(idClassMapping);
    
    jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
    adapter.setMessageConverter(jackson2JsonMessageConverter);
    container.setMessageListener(adapter);
委托類中添加監(jiān)聽方法

在MessageDelegate中添加消息監(jiān)聽方法乌逐,對應(yīng)的參數(shù)類型是Java對象

    public void consumeMessage(Order order) {
        System.err.println("order對象, 消息內(nèi)容, id: " + order.getId() + 
                ", name: " + order.getName() + 
                ", content: "+ order.getContent());
    }
    
    public void consumeMessage(Packaged pack) {
        System.err.println("package對象, 消息內(nèi)容, id: " + pack.getPackageId() + 
                ", name: " + pack.getPackageName() + 
                ", content: "+ pack.getDescription());
    }
測試映射器

編寫測試方法竭讳,注意設(shè)置消息的java對象類型,這里只需要設(shè)置java對象的標(biāo)識即可

    @Test
    public void testSendMappingMessage() throws Exception {
        
        ObjectMapper mapper = new ObjectMapper();
        
        Order order = new Order("003","訂單消息","訂單描述信息");
        String json1 = mapper.writeValueAsString(order);
        System.err.println("order 4 json: " + json1);
        
        MessageProperties messageProperties1 = new MessageProperties();
        //這里注意一定要修改contentType為 application/json
        messageProperties1.setContentType("application/json");
        //只需要設(shè)置java對象的標(biāo)識
        messageProperties1.getHeaders().put("__TypeId__", "order");
        Message message1 = new Message(json1.getBytes(), messageProperties1);
        rabbitTemplate.send("topic001", "spring.order", message1);
        
        Packaged pack = new Packaged("003","包裹消息","包裹描述信息");
        String json2 = mapper.writeValueAsString(pack);
        System.err.println("pack 4 json: " + json2);

        MessageProperties messageProperties2 = new MessageProperties();
        //這里注意一定要修改contentType為 application/json
        messageProperties2.setContentType("application/json");
        messageProperties2.getHeaders().put("__TypeId__", "packaged");
        Message message2 = new Message(json2.getBytes(), messageProperties2);
        
        rabbitTemplate.send("topic001", "spring.pack", message2);
    }

運行測試方法浙踢,控制臺打印內(nèi)容如下绢慢,事實上只打印了前三行,因為SpringBoot運行完Junit測試后就會自動停止了成黄,而不會等消息處理完成之后再關(guān)閉容器呐芥,此時可以再運行Application類就打印了第四行信息,也就是完成了第二條消息的處理奋岁。

order 4 json: {"id":"003","name":"訂單消息","content":"訂單描述信息"}
pack 4 json: {"packageId":"003","packageName":"包裹消息","description":"包裹描述信息"}
order對象, 消息內(nèi)容, id: 003, name: 訂單消息, content: 訂單描述信息
package對象, 消息內(nèi)容, id: 003, name: 包裹消息, content: 包裹描述信息
全局轉(zhuǎn)換器

上述介紹的轉(zhuǎn)換器功能都相對單一思瘟,如果要處理更多場景下的不同類型消息,可以使用全局轉(zhuǎn)換器闻伶。

配置全局轉(zhuǎn)換器
    //全局的轉(zhuǎn)換器滨攻,可以添加各種具體的轉(zhuǎn)換器
    ContentTypeDelegatingMessageConverter convert = new ContentTypeDelegatingMessageConverter();
    //根據(jù)不同的contentType添加相應(yīng)的消息轉(zhuǎn)換器
    //addDelegate(String contentType, MessageConverter messageConverter)
    TextMessageConverter textConvert = new TextMessageConverter();
    convert.addDelegate("text", textConvert);
    convert.addDelegate("html/text", textConvert);
    convert.addDelegate("xml/text", textConvert);
    convert.addDelegate("text/plain", textConvert);
    
    Jackson2JsonMessageConverter jsonConvert = new Jackson2JsonMessageConverter();
    convert.addDelegate("json", jsonConvert);
    convert.addDelegate("application/json", jsonConvert);
    
    ImageMessageConverter imageConverter = new ImageMessageConverter();
    convert.addDelegate("image/jpg", imageConverter);
    convert.addDelegate("image", imageConverter);
    
    PDFMessageConverter pdfConverter = new PDFMessageConverter();
    convert.addDelegate("application/pdf", pdfConverter);
    
    adapter.setMessageConverter(convert);
    container.setMessageListener(adapter);
定義相應(yīng)的轉(zhuǎn)換器

這里以圖片轉(zhuǎn)換器為例,最終轉(zhuǎn)換的類型是File

public class ImageMessageConverter implements MessageConverter {

    @Override
    public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
        throw new MessageConversionException(" convert error ! ");
    }

    @Override
    public Object fromMessage(Message message) throws MessageConversionException {
        System.err.println("-----------Image MessageConverter----------");
        
        Object _extName = message.getMessageProperties().getHeaders().get("extName");
        String extName = _extName == null ? "jpg" : _extName.toString();
        
        byte[] body = message.getBody();
        String fileName = UUID.randomUUID().toString();
        String path = "d:/photo/" + fileName + "." + extName;
        File f = new File(path);
        try {
            Files.copy(new ByteArrayInputStream(body), f.toPath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return f;
    }
}
委托類中添加監(jiān)聽方法
    public void consumeMessage(File file) {
        System.err.println("文件對象方法, 消息內(nèi)容:" + file.getName());
    }
測試

編寫測試方法蓝翰,注意設(shè)置消息的contentType

    @Test
    public void testSendExtConverterMessage() throws Exception {
            byte[] body = Files.readAllBytes(Paths.get("C:/Users/ruoxiyuan/Desktop", "picture.jpg"));
            MessageProperties messageProperties = new MessageProperties();
            messageProperties.setContentType("image/jpg");
            messageProperties.getHeaders().put("extName", "jpg");
            Message message = new Message(body, messageProperties);
            rabbitTemplate.send("", "image_queue", message);
    }

運行測試方法光绕,控制臺打印內(nèi)容如下,其他幾種類型轉(zhuǎn)換器也可以自行驗證

-----------Image MessageConverter----------
文件對象方法, 消息內(nèi)容:35f00409-83ce-4bf0-881a-a3bc5d13f85e.jpg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末畜份,一起剝皮案震驚了整個濱河市诞帐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌爆雹,老刑警劉巖停蕉,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钙态,居然都是意外死亡慧起,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門册倒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚓挤,“玉大人,你說我怎么就攤上這事〔右猓” “怎么了估灿?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脾歧。 經(jīng)常有香客問我甲捏,道長,這世上最難降的妖魔是什么鞭执? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任司顿,我火速辦了婚禮,結(jié)果婚禮上兄纺,老公的妹妹穿的比我還像新娘大溜。我一直安慰自己,他們只是感情好估脆,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布钦奋。 她就那樣靜靜地躺著,像睡著了一般疙赠。 火紅的嫁衣襯著肌膚如雪付材。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天圃阳,我揣著相機與錄音佳励,去河邊找鬼赂乐。 笑死黄痪,一個胖子當(dāng)著我的面吹牛沉衣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锣夹,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼页徐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了银萍?” 一聲冷哼從身側(cè)響起变勇,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贴唇,沒想到半個月后贰锁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡滤蝠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了授嘀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片物咳。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蹄皱,靈堂內(nèi)的尸體忽然破棺而出览闰,到底是詐尸還是另有隱情芯肤,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布压鉴,位于F島的核電站崖咨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏油吭。R本人自食惡果不足惜击蹲,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望婉宰。 院中可真熱鬧歌豺,春花似錦、人聲如沸心包。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蟹腾。三九已至痕惋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娃殖,已是汗流浹背值戳。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留珊随,地道東北人述寡。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像叶洞,于是被迫代替她去往敵國和親鲫凶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容

  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981閱讀 15,899評論 2 11
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,094評論 1 32
  • 1. 簡介 1.1 什么是 MyBatis 衩辟? MyBatis 是支持定制化 SQL螟炫、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,466評論 0 4
  • 一、基礎(chǔ)知識:1艺晴、JVM昼钻、JRE和JDK的區(qū)別:JVM(Java Virtual Machine):java虛擬機...
    殺小賊閱讀 2,371評論 0 4
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當(dāng)在唯一索引所對應(yīng)的列上鍵入重復(fù)值時,會觸發(fā)此異常封寞。 O...
    我想起個好名字閱讀 5,261評論 0 9