Spring AMQPでオレオレErrorHandlerを使ってみるテスト

@RabbitListeneを使っている時にデフォルトのErrorHandlerではなくて、独自処理を実装したErrorHandlerを使う方法を調べてみたので自分用メモ。

設計も汚いし、とにかく動くところまでしか確認してない。

package hello;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.ErrorHandler;

@SpringBootApplication
@EnableRabbit
public class Application implements RabbitListenerConfigurer {

    @Autowired
    ConnectionFactory connectionFactory;

    @Bean
    MessageListenerAdapter listenerAdapter(Receiver receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setMessageConverter(jackson2Converter());
        return factory;
    }

    @Bean
    public MappingJackson2MessageConverter jackson2Converter() {
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        return converter;
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setErrorHandler(errorHandler());
        registrar.setContainerFactory(factory);
        registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
    }

    public ErrorHandler errorHandler() {
        return new MyHandler();
    }

    @Slf4j
    static class MyHandler implements ErrorHandler {

        ErrorHandler errorHandler = new ConditionalRejectingErrorHandler();

        @Override
        public void handleError(Throwable t) {
            log.error("==do something for error==");
            errorHandler.handleError(t);
        }

    }

}

@Service
@Slf4j
class Receiver {

    @RabbitListener(queues = "spring-boot")
    public void receiveMessage(SampleDto dto) {
        log.info("Received <" + dto.toString() + ">");
    }

}

@Data
@NoArgsConstructor
@AllArgsConstructor
class SampleDto {

    private String key;
    private String value;
}

build.gradleはこんな感じ

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.0.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
    baseName = 'messaging-rabbitmq-oreore'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    compile("org.springframework.boot:spring-boot-starter-amqp")
    compile group: 'org.projectlombok', name: 'lombok', version: '1.16.10'

    testCompile("junit:junit")
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}

ext.mainClass  = 'hello.Application'

あとテストように作ったpublisherも貼っておく

package hello;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.stereotype.Service;

@SpringBootApplication
public class Application implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.exit(SpringApplication.run(Application.class, args));
    }

    @Bean
    Queue queue() {
        return new Queue("spring-boot", true);
    }

    @Bean
    DirectExchange exchange() {
        return new DirectExchange("direct");
    }

    @Bean
    Binding binding(Queue queue, DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).withQueueName();
    }

    @Bean
    public MappingJackson2MessageConverter jackson2Converter() {
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        return converter;
    }

    @Autowired
    private Sender sender;

    @Override
    public void run(String... args) throws Exception {
        for (String i : args) {
            ObjectMapper mapper = new ObjectMapper();
            SampleDto dto = null;
            try {
                dto = mapper.readValue(i, SampleDto.class);
                sender.sendToRabbitmqAsJson(dto);
            } catch (Exception ex) {
                sender.sendToRabbitmqAsString(i);
            }
        }

    }
}

@Service
class Sender {

    @Autowired
    private RabbitMessagingTemplate rabbitMessagingTemplateAsJson;

    @Autowired
    private RabbitMessagingTemplate rabbitMessagingTemplateAsString;

    @Autowired
    private MappingJackson2MessageConverter mappingJackson2MessageConverter;

    public void sendToRabbitmqAsJson(final SampleDto dto) {

        this.rabbitMessagingTemplateAsJson.setMessageConverter(this.mappingJackson2MessageConverter);

        this.rabbitMessagingTemplateAsJson.convertAndSend("direct", "spring-boot", dto);

    }

    public void sendToRabbitmqAsString(final String message) {

        this.rabbitMessagingTemplateAsString.convertAndSend("direct", "spring-boot", message);

    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class SampleDto {

    private String key;
    private String value;
}

rasbperry pi3でNAS構築(とついでにbonding)

ご無沙汰してます。

サーバの勉強にもなるかなと思ってraspberry pi3を手に入れて自作(かつ自宅)NASサーバを構築してみました!

このraspberry pi3なんですけど、無線LAN付きになったんですよ。

USBドングル買わなくても無線化できるので少し財布に優しいー!

さて、そのNASサーバの構築ですがざっくりこんな流れです。

  1. raspberry piをSSHできるようにする
  2. USB HDDをマウントする
  3. smbインストールしてNASにする
  4. 有線LAN+無線LANのbondingの設定をする

この中の(1)〜(3)の手順は検索すればraspberry pi2のものですが情報がザクザク(でもないかもしれないけど)出てくるのでは書くのはやめるとして、、、

(4)の『bondingの設定をする』に絞って手順をご紹介します。

bondingの設定自体はraspberry pi bondingで検索すると、 以下のページが出てくるので、主にこの2つのページを参考に設定しました。

補助記憶: Raspberry PiでUSB-Wifiアダプタを使う

d.hatena.ne.jp

しかーし、紹介されたとおりに設定してみても紹介されている通りに動作しない… どう、動作しないかというと

  1. 上記のページで書かれている通り、bond0にだけIPが振られるわけじゃなかった(eth0とwlan0にもIP振られちゃうけど、無視して設定進めたらbondingは動作した)
  2. LANケーブルさしてないとsshが繋がらない
  3. LANケーブル挿すとping返ってくるけど通信速度が変わらない

という具合です。

tcpdump見たわけじゃないんですが、有線LAN抜くと動作しないってことは、 bonding用のインターフェース(bond0)に割り付けたIPに対するリクエストの返りが有線LANのインターフェース(eth0)から出ていってるんじゃないかと推測して ルーティングテーブル周りの設定変更を試してみたら、うまく動作した!っていうメモです。

たぶん、正しい手順の解決策ではないと思いますが、自分用のメモ(と、他に困ってる人の参考)に参考になったページのリンクを残しておきます。

似たような現象を探してて見つけたページがこちら。

ja.stackoverflow.com

このページを見て、ルーティングテーブルか何かがおかしいのでは、と気づき ip ruleで検索してでてきた

d.hatena.ne.jp

このページに倣って設定したら、動作しました。

こんな感じ!

Before(といいながら有線LAN抜いた時の速度です。スクショ撮り忘れた…)

f:id:kogayushi:20160429151525p:plain

After

f:id:kogayushi:20160429151620p:plain

特定のクラスを継承したクラス一覧を出力する方法

掲題の通り、EclipseとかNetBeansの力を借りずにコードを書いてリストを出してみました。

guavaのClassPath使えばこんな感じで簡単に出せるようです。

package tester;

import com.google.common.reflect.ClassPath;

import java.io.IOException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.apache.struts.action.Action;

/**
 *
 * @author yushi.koga
 */
public class Main {

    public static void main(String... args) {
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            Set<Class<?>> allClasses = ClassPath.from(loader)
                    .getTopLevelClassesRecursive("my.package").stream()
                    .map(info -> info.load())
                    .collect(Collectors.toSet());
            allClasses.stream().filter((actionClass) -> (Action.class.isAssignableFrom(actionClass))).forEach((actionClass) -> {
                System.out.println(actionClass.toString());
            });

        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

RTrim最速っぽいのを簡単に調べてみた

自宅の環境(CPU:Core2 Duo E6750 メモリ4G)ではStringUtils#stripEndが最速らしい。 しかし、会社のPCではguavaが最速でした。

検証したのは、

  • 処理時に(メソッド呼ばれる毎に)で毎回Patternのインスタンス生成からやる
  • Patternの変数をstaticで持たせてインスタンス生成のコストを減らしたもの
  • StringUtils#stripEnd
  • CharMatcher#trimTrailingFrom

のどれが一番早いか!です。

結果はこちら。

検証コード

package speedcheck;

import com.google.common.base.CharMatcher;

import org.apache.commons.lang.StringUtils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author yushi.koga
 */
public class SpeedCheck {

    static Pattern staticPatternForRtrim = Pattern.compile("\\s+$");
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        String sampleText = "\"aaaaaa\"    ";
        int max = 25000000;

        String staticTrimedText = null;
        System.out.println("original text start from here ->" + "\"aaaaaa\"    " + "<-to end is here");
        System.out.println("loop times : " + max);
        System.out.println("=======================================");
        long staticStarted = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            Matcher staticMatcher = staticPatternForRtrim.matcher(sampleText);
            staticTrimedText = staticMatcher.replaceAll("");
        }
        long staticFinished = System.currentTimeMillis();
        System.out.println("staticStarted      at " + staticStarted);
        System.out.println("staticfinished     at " + staticFinished);
        System.out.println("staticPassed time  is " + ((staticFinished - staticStarted)));
        System.out.println("staticTrimedText   is " + staticTrimedText + "<- end here");
        System.out.println("=======================================");


        String insideTrimedText = null;
        long insideStarted = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            insideTrimedText = Pattern.compile("\\s+$").matcher(sampleText).replaceAll("");
        }
        long insideFinished = System.currentTimeMillis();
        System.out.println("insideStarted      at " + insideStarted);
        System.out.println("insidefinished     at " + insideFinished);
        System.out.println("insidePassed time  is " + ((insideFinished - insideStarted)));
        System.out.println("insideTrimedText   is " + insideTrimedText + "<- end here");
        System.out.println("=======================================");


        String commonsTrimedText = null;
        long commonsStarted = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            commonsTrimedText = StringUtils.stripEnd(sampleText," ");
        }
        long commonsFinished = System.currentTimeMillis();
        System.out.println("commonsStarted     at " + commonsStarted);
        System.out.println("commonsFinished    at " + commonsFinished);
        System.out.println("commonsPassed time is " + ((commonsFinished - commonsStarted)));
        System.out.println("commonsTrimedText  is " + commonsTrimedText + "<- end here");
        System.out.println("=======================================");


        String charMatcherTrimedText = null;
        long charMatcherStarted = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            charMatcherTrimedText = CharMatcher.is(' ').trimTrailingFrom(sampleText);
        }
        long charMatcherFinished = System.currentTimeMillis();
        System.out.println("quavaStarted       at " + charMatcherStarted);
        System.out.println("quavaFinished      at " + charMatcherFinished);
        System.out.println("quavaPassed time   is " + ((charMatcherFinished - charMatcherStarted)));
        System.out.println("quavaTrimedText    is " + charMatcherTrimedText + "<- end here");
        System.out.println("=======================================");
    }

}

結果

Executing: gradle :run
Arguments: [-PmainClass=speedcheck.SpeedCheck, -c, D:\workspaceForNetBeans\speedcheck\settings.gradle]

:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
original text start from here ->"aaaaaa"    <-to end is here
loop times : 25000000
=======================================
staticStarted      at 1448538448194
staticfinished     at 1448538458284
staticPassed time  is 10090
staticTrimedText   is "aaaaaa"<- end here
=======================================
insideStarted      at 1448538458285
insidefinished     at 1448538479871
insidePassed time  is 21586
insideTrimedText   is "aaaaaa"<- end here
=======================================
commonsStarted     at 1448538479871
commonsFinished    at 1448538480760
commonsPassed time is 889
commonsTrimedText  is "aaaaaa"<- end here
=======================================
quavaStarted       at 1448538480760
quavaFinished      at 1448538483150
quavaPassed time   is 2390
quavaTrimedText    is "aaaaaa"<- end here
=======================================

BUILD SUCCESSFUL

Total time: 35.862 secs

どうしてもNIO2が使えないのでムシャクシャしてやった

仕事でですが、どうしてもJava6の環境で、ファイルの更新管理をするライブラリを作らなきゃいけなかったので、Apache Commons IO使って作ってみた。

github.com

参考にしたのはここです。 ありがとうございました。

qiita.com

実際に使うときはもっと修正していますが、サンプルにどうぞ。

ResourceMonitor

package sample.file.monitor.monitor;

import java.io.File;
import java.io.FileFilter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.log4j.Logger;
import sample.file.monitor.listner.ResourceListner;
import sample.file.monitor.resource.Resource;

/**
 *
 * @author yushi.koga
 */
public class ResourceMonitor {

    static private final ResourceMonitor resourceMonitor = new ResourceMonitor();
    static private final Logger log = Logger.getLogger(ResourceMonitor.class);
    private long refreshDelay = 4000L;
    private URI monitoringDirectory = null;
    static private final String MONITORING_FILE = "target.txt";

    public static ResourceMonitor getInstance() {
        return resourceMonitor;
    }

    public void setRefreshDelay(long refreshDelay) {
        this.refreshDelay = refreshDelay;
    }

    public void setMonitoringDirectory(URI monitoringDirectory) throws URISyntaxException, MalformedURLException {
        this.monitoringDirectory = monitoringDirectory;
    }

    private ResourceMonitor() {
    }

    public void start() throws ConfigurationException, MalformedURLException, Exception {
        // Guard Clause
        if (monitoringDirectory == null) {
            throw new IllegalStateException("Please set monitoring directory");
        }
        // Generate Monitor. Interval is milli seconds.
        FileAlterationMonitor monitor = new FileAlterationMonitor(refreshDelay);

        // Generate Observer.Set monitoring directory.
        final File dir = new File(monitoringDirectory);
        FileFilter filter = new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().equals(MONITORING_FILE);
            }
        };
        FileAlterationObserver observer = new FileAlterationObserver(dir, filter);

        // Generate and register Lister to Observer
        ResourceListner listener = new ResourceListner();
        observer.addListener(listener);

        // Register Observer to Monitor
        monitor.addObserver(observer);

        // Start Monitor
        Resource.load(new URL(monitoringDirectory.toURL(), MONITORING_FILE));
        monitor.start();
        log.info(this.getClass().getSimpleName() + " started monitoring");
    }
}

ResourceListner

package sample.file.monitor.listner;

import java.io.File;
import java.net.MalformedURLException;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.log4j.Logger;
import sample.file.monitor.resource.Resource;

/**
 *
 * @author yushi.koga
 */
public class ResourceListner extends FileAlterationListenerAdaptor {

    private final Logger log = Logger.getLogger(ResourceListner.class);

    private void load(File file, String event) {
        try {
            Resource.load(file.toURI().toURL());
        } catch (MalformedURLException ex) {
            log.error(file.getName() + "is " + event + "but failed to load ", ex);
        } catch (ConfigurationException ex) {
            log.error(file.getName() + "is " + event + "but failed to load ", ex);
        }
    }

    @Override
    public void onFileChange(File file) {
        log.info(file.getName() + "is updated");
        load(file, "updated");

    }

    @Override
    public void onFileCreate(File file) {
        log.info(file.getName() + "is created");
        load(file, "created");
    }

    @Override
    public void onFileDelete(File file) {
        log.info(file.getName() + "is deleted");
    }

}
Resource
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package sample.file.monitor.resource;

import java.net.URL;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.log4j.Logger;

/**
 *
 * @author yushi.koga
 */
public class Resource {

    static private final Resource resource = new Resource();
    static private final Logger log = Logger.getLogger(Resource.class);
    static private PropertiesConfiguration configuration;

    private Resource() {
    }

    public static Resource getInstance() {
        return resource;
    }

    public static void load(URL resourceFile) throws ConfigurationException{
        try {
            configuration = new PropertiesConfiguration(resourceFile);
        } catch (ConfigurationException ex) {
            log.error("failde to load " + resourceFile.toString(),ex);
            throw new ConfigurationException(ex);
        }
    }

    public String getString(String key) {
        return configuration.getString(key);
    }

    public int getInt(String key) {
        return configuration.getInt(key);
    }

    public long getLong(String key) {
        return configuration.getLong(key);
    }
}

WindowsとMacの両方でローカル配信メールを設定する方法

ローカルからアプリケーションがメール投げようとすると何も設定してないとエラーになっちゃいますよね。 私の職場じゃWindowsMac使っている人が半々なのですが、どちらの環境でもこのメールの問題を解決したので書いてみます。

Windowsの場合

簡単です。 smtp4devをダウンロードして起動するだけで25ポートをlistenしてくれます。 あとはアプリがメール投げたらこの子がそれを拾ってくれるようになります。

smtp4dev.codeplex.com

Macの場合

Macの場合は、OSがUnix系なので、postfixを設定します。 設定をすると/Users/{username}/Maildir/の配下にメールが届くようになります。 こんな感じ。

# command : create setting file for postfix
$ sudo vi /etc/postfix/aliases.reg

## discription
/(?!^root$|^{username}$)^.*$/ {username}

# command : create setting file for postfix
$ sudo vi /etc/postfix/transport_maps

## discription
/^.*@.*$/ local

# command : modify setting file for postfix
$ sudo vi /etc/postfix/main.cf

## discription
#local_recipient_maps =#local_recipient_maps =
local_recipient_maps =

#alias_maps = netinfo:/aliases#alias_maps = netinfo:/aliases
alias_maps = hash:/etc/postfix/aliases,regexp:/etc/postfix/aliases.reg

#home_mailbox = Mailbox#home_mailbox = Mailbox
home_mailbox = Maildir/

#luser_relay = admin+$local#luser_relay = admin+$local
luser_relay = {username}

# add following description to bottom line
transport_maps = pcre:/etc/postfix/transport_maps

# command : create aliases's binaly file
$ sudo postalias /etc/postfix/aliases

# command : reflect
$ sudo postfix reload

{username}は各自のユーザ名に置き換えてください。

postfixが起動してないとsudo postalias /etc/postfix/aliasesの箇所でエラーになります。

その場合は、sudo postfix startしてあげてください。

参考にしたサイトはこちら。

https://blog.tagbangers.co.jp/ja/2015/05/22/setting-of-local-e-mail-for-mac

http://open-groove.net/postfix/mail-forward/

CentOS7でLAMP環境構築(といいつつnginx)

友人のサイトのローカル環境を構築するのにLAMP環境をMacに入れたので、参考にしたサイトをメモ代わりに貼っておく。

CentOS 7 でLAMP(Nginx+MariaDB(MySQL)+PHP)インストールからWordPressを動かすまで(Nginx編) | レンタルサーバー・自宅サーバー設定・構築のヒント