tokuhirom's blog

Java で method_missing したい。

Java で method_missing したいというのは、テストを書いているときによくあるケースである。 Mockito など利用すればいいのだが、その中身が気になってしまうのが、エンジニアの性であろう。

そういうわけで、method_missing のやり方を調べていると、Proxy を利用する例が多々みつかる。 しかしこの方法では満足な結果は得られない。なぜならば Proxy では interface に対するモッキングはできるがクラスにたいするモッキングが出来ないからだ。悲しい。

参考: http://www.javaworld.com/article/2076233/java-se/explore-the-dynamic-proxy-api.html

そういうわけで、mocking には cglib or javassist などのバイトコード生成系ライブラリを利用するのが良い。 http://stackoverflow.com/questions/3291637/alternatives-to-java-lang-reflect-proxy-for-creating-proxies-of-abstract-classes


なお mockito の

when(body).thenReturn()

のような記法で、body がキャプチャできるわけないしキモいなあと思っていたのだが、これは単に DSL としてかっこ良く見せてるだけで、実際には引数としてなにかに利用しているわけではないようだ。つまり以下のように書いても同じなのだが、読みやすくするためにああなっている、ということのようだ。

body; when().thenReturn()
Created: 2016-02-01 10:58:26
Updated: 2016-02-01 10:58:26

spring boot で、テスト時だけ読ませたい @Configuration があるとき

なんかよくわからんけど @ContextConfiguration 指定すると @SpringApplicationConfiguration が処理されなくなるのかな。よくわからんけど。

なので、@SpringApplicationConfiguration(classes = { MyApplication.class, MyConfig1.class, MyConfig2.class }) みたいにしておいて、MyConfig1.class を test.MyConfig1 など、component scan の対象外に追い出せば良い。

以上。

Created: 2016-01-25 16:39:04
Updated: 2016-01-25 16:39:04

spring の MockMVC で通信結果をダンプしたいとき

mockmvc.perform(get("/"))
    .andDo(log());

とかしとけばいい。

    .andDo(print());

のほうが、確実に出力されるのでいいかも(ロガーの設定によらず出力される)

Created: 2016-01-25 16:12:17
Updated: 2016-01-25 16:12:17

spring boot でテスト書いてたら 403 になって、csrf filter に引っかかってるぽくていらっとしたときの対策

@Before
public void setUp() throws Exception {
    mockMvc = MockMvcBuilders
            .webAppContextSetup(webApplicationContext)
            .apply(springSecurity())
            .build();
}

みたいな設定してるときに csrf filter かかってきてテストコードが 403 で弾かれてうざいわーって時

mockMvc.perform(post("/register")
              .with(csrf())
              .param("action", "signup"))
     .andExpect(status().isOk());

とかすりゃ、とりあえず csrf token 渡すようになるっぽい。

って stackoverflow に書いてあった。

http://stackoverflow.com/questions/21749781/why-i-received-an-error-403-with-mockmvc-and-junit

Created: 2016-01-25 16:03:58
Updated: 2016-01-25 16:03:58

Spring Boot で redirect: とかなってるやつを自分で拡張したい

通常の Spring Boot では redirect:/ とか書くと、Moved Temporaly になるけど Moved Permanently 返したいときがある。

そんなとき、以下のような答えがインターネッツを検索していると出てくる。

  • @ResponseStatus(value=HttpStatus.MOVED_PERMANENTLY) ってコントローラメソッドに付与するって方法がある
    • メソッド全体にかかってくるのがダサい。
  • httpServletReqeust.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, 302) でやる
    • わかりづらい

そういうわけで、redirect301:/hoge/ とかでできるようにしたらいいんじゃないかと思いつく。 この似非 URL みたいなやつは ViewResolver ってやつがハンドリングしている。

ViewResolver は https://github.com/spring-projects/spring-framework/blob/554bf49/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java#L700 のへんで集められるが、要するに DI で ViewResolver をねじ込めば良い。

Spring Boot の場合、以下のようにすればよい。ViewResolver は複数指定できて、順番に呼び出される感じになっている。apache module のような呼びだされ方を想定しておけば良い。

@Slf4j
@Configuration
public class WebConfig implements ServletContextAware {
    @Bean
    public ViewResolver myViewResolver() {
        return (viewName, locale) -> {
            final String header = "redirect301:";
            if (viewName.startsWith(header)) {
                String redirectUrl = viewName.substring(header.length());
                RedirectView view = new RedirectView(redirectUrl, true, true);
                view.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
                return view;
            } else {
                return null; // DECLINED
            }
        };
    }
}
Created: 2016-01-12 18:27:33
Updated: 2016-01-12 18:27:33

Harriet 0.05 released.

shipped.

Created: 2016-01-08 18:36:45
Updated: 2016-01-08 18:36:45

spring mvc の interceptor のパスマッチャのやつ

Spring mvc の interceptor ってやつ。hook みたいなやつ。以下のようなパスの設定ができる。 excludePathPatterns の設定だけだと動かないとかいう説もあって、注意が必要。

registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");

http://docs.spring.io/spring/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-config-interceptors

ここのパターンは PathMatcher でマッチするのだが、AntPathMatcher が事実上唯一の実装。 https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/AntPathMatcher.html このマッチャで利用可能なメタキャラクタは以下のもののみ。

? matches one character
* matches zero or more characters
** matches zero or more directories in a path

インターセプターで、特定のパスのみ認証スキップとかやってる場合、気をつけないとエラー表示ページとかがそのまま interceptor かかってて謎のリダイレクトかかったりしてだるいです。

以上。

Created: 2015-12-24 19:01:52
Updated: 2015-12-24 19:01:52

AssertJ 用の IntelliJ postfix completion plugin を書いた

AssertJ の assertThat() をpostfix completion で入力したいと常々思っていて、誰か書いてくれないかなと思っていたけど誰も書いてくれないし、IntelliJ のプラグインというものを書いてみたかったので書いてみた。

https://github.com/tokuhirom/assertj-postfix-plugin

↑レポジトリはこちら。

IntelliJ のプラグインの情報は少なくて、どうもどこが本家なのかよくわからなかった。 http://www.jetbrains.org/intellij/sdk/docs/index.html たぶんこの辺。だと思う。

とりあえず検索してみたところ IntelliJ IDEAのPostfix補完プラグインを作るという記事が見つかったので、「こりゃあ、真似すれば簡単に実装できそうだなー」と思ったのが運の尽き。

どーも、ここに書いてある情報だけでは実装は無理っぽい。。 なんか実行時例外出るし。。そもそも俺 intellij plugin の書き方が全くわかってないし。。

で、いろいろ調べてみたところ、IntelliJ のプラグインは gradle つかって開発するのが楽っぽい。

gradle-intellij-plugin ってので、簡単にビルドできる。./gradlew runIdea ってすると、gradle が intellij idea のコミュニティエディションをダウンロードしてきてそれを実行してくれる。

https://github.com/JetBrains/gradle-intellij-plugin

build.gradle は以下の様な感じ。


plugins {
    id "org.jetbrains.intellij" version "0.0.32"
}

apply plugin: 'org.jetbrains.intellij'
apply plugin: 'java'


sourceCompatibility = 1.8

intellij {
    pluginName 'AssertJ Postfix Plugin'
}

group 'me.geso.assertj_postfix_plugin'
version '0.0.1'

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

repositories {
    mavenCentral()
}

ディレクトリ構成は以下のようになっている。

src
└── main
    ├── java
    │   └── me
    │       └── geso
    │           └── assertj_postfix_plugin
    │               ├── AssertJPostfixTemplateProvider.java
    │               └── AssertJTemplate.java
    └── resources
        ├── META-INF
        │   └── plugin.xml
        └── postfixTemplates
            └── AssertJTemplate
                ├── after.java.template
                ├── before.java.template
                └── description.html

src/main/resources/postfixTemplates/ 以下にテンプレートのデータが入っている。これは実際になにかの役に立つわけではないが、入れておかないと例外があがって死ぬ。各テンプレートごとに after.java.template, before.java.template, description.html の3つのファイルが必要だ。 これらのテンプレートは、以下のような場所に表示される。表示されるだけなので、動作には関係ない。

META-INF/plugin.xml がプラグインのメタデータ。プラグインのIDやらなんやらの基本的な設定がここに書いてある。

codeInsight.template.postfixTemplateProvider のあたりが重要で、テンプレートプロバイダを設定している。あとは他のプラグインでも一緒。

<idea-plugin version="2">
    <id>me.geso.assertj_postfix_plugin</id>
    <name>AssertJ Postfix Completion Plugin</name>
    <version>0.0.1</version>
    <vendor email="tokuhirom+intellij@gmail.com" url="http://64p.org">tokuhirom</vendor>

    <description><![CDATA[
        This plugin adds postfix completion template for assertj.
    ]]></description>

    <change-notes><![CDATA[
        <ul>
        <li>0.0.1 - The first release.
        </ul>
    ]]>
    </change-notes>

    <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description -->
    <idea-version since-build="141.0"/>

    <extensions defaultExtensionNs="com.intellij">
        <codeInsight.template.postfixTemplateProvider language="JAVA"
                                                      implementationClass="me.geso.assertj_postfix_plugin.AssertJPostfixTemplateProvider"/>
    </extensions>

    <actions>
    </actions>

</idea-plugin>

で、テンプレートの入り口は以下のとおり。テンプレートのセットを作って返しているだけ。

package me.geso.assertj_postfix_plugin;

import java.util.HashSet;
import java.util.Set;

import org.jetbrains.annotations.NotNull;

import com.intellij.codeInsight.template.postfix.templates.JavaPostfixTemplateProvider;
import com.intellij.codeInsight.template.postfix.templates.PostfixTemplate;

public class AssertJPostfixTemplateProvider extends JavaPostfixTemplateProvider {
    private final HashSet<PostfixTemplate> templates;

    public AssertJPostfixTemplateProvider() {
        templates = new HashSet<>();
        templates.add(new AssertJTemplate());
    }

    @NotNull
    @Override
    public Set<PostfixTemplate> getTemplates() {
        return templates;
    }
}

テンプレートの実装、以下の様な感じになる。StringBasedPostfixTemplate ってやつを継承することにより、なんかそれっぽいテンプレートを書くだけでよいという感じになっている。

package me.geso.assertj_postfix_plugin;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import com.intellij.codeInsight.template.Template;
import com.intellij.codeInsight.template.Template.Property;
import com.intellij.codeInsight.template.TemplateManager;
import com.intellij.codeInsight.template.postfix.templates.StringBasedPostfixTemplate;
import com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils;
import com.intellij.psi.PsiElement;

public class AssertJTemplate extends StringBasedPostfixTemplate {

    public AssertJTemplate() {
        super("assertThat", "assertThat(expr);",
             JavaPostfixTemplatesUtils.selectorAllExpressionsWithCurrentOffset(JavaPostfixTemplatesUtils.IS_NON_VOID));
    }

    @Override
    public Template createTemplate(TemplateManager templateManager, String s) {
        Template template = super.createTemplate(templateManager, s);
        template.setValue(Property.USE_STATIC_IMPORT_IF_POSSIBLE, true);
        return template;
    }

    @Nullable
    @Override
    public String getTemplateString(@NotNull PsiElement psiElement) {
        return  "org.assertj.core.api.Assertions.assertThat($expr$);$END$";
    }
}

デバッガで実行すれば、プラグインが読み込まれた状態の IDEA が起動するので、動作を確認すればOK。普通の java アプリと同様に、デバッガでステップ実行とかもできる。便利。

出来上がったら、 https://plugins.jetbrains.com/ で publish すれば OK.

./gradlew publishPlugin やらでアップロードできるが、とりあえずは普通に ./gradlew buildPlugin して、form からアップロードした。plugin.xml が含まれてないと言われてアップロード失敗したので、./gradlew jar した jar をアップした。二回目からは publishPlugin でやれる。と思う。

そういうわけで、アップしてみてある。ご利用ください。と言いたいところだけど、まだ承認待なのでした。 https://plugins.jetbrains.com/plugin/8093?pr=

Created: 2015-12-14 12:30:07
Updated: 2015-12-14 12:30:07

spring boot で、freemarker のテンプレートの読み込みがサーバー再起動しないと読まれなくてだるいって時の対処方法

spring boot はデフォルトでは classpath から読んでるので、まあなんか再読み込みさせるのが難しい。eclipse なら自動でコンパイルされるのでまだマシだが、intellij だとなんか遅いしイライラする。

そこで、build.gradleに以下のように記述し、template の読み込みパスを変更した。

bootRun {
    systemProperty "spring.freemarker.templateLoaderPath", "file:src/main/resources/templates/,classpath:/templates/"
}

freemarker が適当に file:// から直接読んでくれるので、ファイル変更したらすぐ読み込まれて便利。なぜ application.properties or application.yml に書かないのかというと、application.yml 的なものは common module に入れておいて、web module, admin module などがそれに依存するみたいな構成にしたいからで、そのような構成になっている場合、common module に sub project のパスを直ガキできないからです。

まあ、そんな感じ。

Created: 2015-12-14 11:07:47
Updated: 2015-12-14 11:07:47

xfce4で、スタートメニューを開くコマンド

xfce4-popup-whiskermenu

Created: 2015-12-12 05:59:53
Updated: 2015-12-12 05:59:53
Next page