Unmodifiable – Java Puzzlers Advent Calendar3日目

import java.util.*;
 
public class Main {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<>(Arrays.asList("aaa", "bbb", "ccc"));
        List<String> unmodifiableList = Collections.unmodifiableList(strings);
 
        System.out.print(unmodifiableList.size());
        System.out.print(", ");
        strings.remove("aaa");
 
        System.out.print(unmodifiableList.size());
    }
}

上のコードを実行した時の結果はどれになるでしょうか?

続きを読む

Equals Method Overloading - Java Puzzlers Advent Calendar1日目

Equals Method Overloading

import java.util.*;
 
class Student {
    private int id;
 
    Student(int id) {
        this.id = id;
    }
 
    public boolean equals(Student student) {
        return student != null && student.id == id;
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}
 
public class Main {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student(1));
        students.add(new Student(2));
 
        students.remove(new Student(1));
 
        System.out.println(students.size());
  }
}
続きを読む

SpongeプラグインでSpringのDIコンテナを使う

MinecraftのSpongeプラグインでSpringを使ってみたのでその時のメモです。

Springを使うメリット ・テストがやりやすくなる ・AOPが使える←個人的にかなり重要

なぜAOPを使いたいか 継承では解決できない同じような処理がサーバーのプラグインでは結構出てくる(ロギングとか権限周り) その辺をAOPで解決したい

プラグインのエントリポイント

package net.orekyuu.spring;
 
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameConstructionEvent;
import org.spongepowered.api.plugin.Plugin;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
@Plugin(
        id = "net.orekyuu.spring",
        name = "SpringDemo",
        version = "1.0-SNAPSHOT"
)
public class SpringDemo {
 
    private ApplicationContext context;
 
    private static SpringDemo INSTANCE;
 
    @Listener
    public void onServerStart(GameConstructionEvent event) {
        INSTANCE = this;
        //net.orekyuu.spring以下をComponentScanしてる
        context = new AnnotationConfigApplicationContext("net.orekyuu.spring");
    }
 
    public static SpringDemo getInstance() {
        return INSTANCE;
    }
 
}

ゲーム起動時にApplicationContextを作るだけです。 プラグイン以下パッケージをComponentScanするようにしておくと、アノテーションベースの設定ができるようになります。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>net.orekyuu</groupId>
    <artifactId>spring</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>Spring</name>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
 
    <profiles>
        <profile>
            <id>release</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-source-plugin</artifactId>
                        <version>2.4</version>
                        <executions>
                            <execution>
                                <id>attach-sources</id>
                                <goals>
                                    <goal>jar-no-fork</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <version>2.10.3</version>
                        <executions>
                            <execution>
                                <id>attach-javadocs</id>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>1.6</version>
                        <executions>
                            <execution>
                                <id>sign-artifacts</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
                <resources>
                    <resource>
                        <directory>src/main/resources</directory>
                        <filtering>true</filtering>
                    </resource>
                </resources>
            </build>
        </profile>
    </profiles>
 
    <build>
        <resources>
            <resource>
                <directory>\${project.basedir}/src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>templating-maven-plugin</artifactId>
                <version>1.0-alpha-3</version>
                <executions>
                    <execution>
                        <id>filter-src</id>
                        <goals>
                            <goal>filter-sources</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.3</version>
                <dependencies>
 
                    <dependency>
                        <groupId>net.trajano.wagon</groupId>
                        <artifactId>wagon-git</artifactId>
                        <version>2.0.3</version>
                    </dependency>
                    <dependency>
                        <groupId>org.apache.maven.doxia</groupId>
                        <artifactId>doxia-module-markdown</artifactId>
                        <version>1.6</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <artifactId>maven-release-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <autoVersionSubmodules>true</autoVersionSubmodules>
                    <tagNameFormat>@{project.version}</tagNameFormat>
                    <scmCommentPrefix xml:space="preserve">[RELEASE] </scmCommentPrefix>
                    <goals>install deploy site-deploy
                    </goals> <!-- install is here to fix javadoc generation in multi-module projects -->
                    <releaseProfiles>release</releaseProfiles>
                </configuration>
            </plugin>
 
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <artifactSet>
                                <excludes>
                                    <exclude>junit:junit</exclude>
                                </excludes>
                            </artifactSet>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <relocations>
                        <relocation>
                            <pattern>org.apache</pattern>
                            <!--org.apacheパッケージのクラスをforgeが読み込んでくれないっぽいので回避-->
                            <shadedPattern>org.aapache</shadedPattern>
                        </relocation>
                    </relocations>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
    <repositories>
        <repository>
            <id>spongepowered-repo</id>
            <url>http://repo.spongepowered.org/maven/</url>
        </repository>
    </repositories>
 
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.spongepowered</groupId>
            <artifactId>spongeapi</artifactId>
            <version>4.1.0-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.2.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.0.13</version>
        </dependency>
 
        <!--AOP-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>RELEASE</version>
        </dependency>
 
    </dependencies>
 
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>2.10.3</version>
            </plugin>
        </plugins>
    </reporting>
</project>

これがpom.xmlです。 注意としてはそのままflat-jarにしてしまうとClassNotFoundExceptionが発生してしまいます。 原因はMinecraftのLaunchClassLoaderにあります。このClassLoaderでModが読み込まれるのですが、org.apache.logging以下パッケージが無視されています。正確にはLaunchClassLoader.class.getClassLoader()でロードされるのですが、LaunchClassLoaderとは親子関係がなくて見つからないみたいな流れ?

そのため解決策としてmaven-shade-pluginを使ってorg.apacheパッケージをリネームすることで回避しました。

イベントをSpringで Spongeで発生するイベントをSpringのイベントに置き換えてみます。

@Component
public class EventConverter {
 
    @Autowired
    private ApplicationEventPublisher publisher;
 
    @Listener
    public void onSpongeEvent(Event event) {
        //SpongeのイベントをSpringに流す
        publisher.publishEvent(new SpongeEvent<>(SpringDemo.getInstance(), event));
    }
}

このComponentはSpongeのイベントをすべて受け取り、Springのイベントへ変換してイベントを通知します。

public class SpongeEvent<T extends Event> extends ApplicationEvent {
 
    private final T event;
 
    public SpongeEvent(Object source, T event) {
        super(source); //とりあえず適当に渡しておく
        Objects.requireNonNull(event);
        this.event = event;
    }
 
    public T getEvent() {
        return event;
    }
}

Spongeのイベントを持つだけのラッパークラスを作りました。ここは特に解説は必要ないと思います。

次に受け取り側です。

//プレイヤーがサーバーに入った時のイベントを受け取ってなにかする
@Component
public class PlayerJoinEventListener {
 
    @Autowired
    public PlayerMessageService playerMessageService;
 
    @EventListener
    public void handleOnPlayerJoin(SpongeEvent<ClientConnectionEvent.Join> event) {
        playerMessageService.sendHelloMessage(event.getEvent().getTargetEntity());
    }
}

Playerが入ってきた時のイベントを取ってみました。 受け取りたいイベントを引数にとって、EventListenerアノテーションをつけるだけです。

Spongeのイベント以外にも独自のイベントを作りたい場合はApplicationEventを継承したクラスを作り、ApplicationEventPublisherへ流すだけです。 詳しく知りたい方はこちらを見るのが良いかと思います。 https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2

最後に設定です。

@Configuration
public class EventConfig {
 
    @Autowired
    private EventConverter eventConverter;
 
    //EventConverterをEventManagerに登録する
    @Bean
    public EventManager eventManager() {
        EventManager manager = Sponge.getEventManager();
        manager.registerListeners(SpringDemo.getInstance(), eventConverter);
        return manager;
    }
 
}

SpongeのEventManagerにEventConverterを登録しているだけです。

SpringAOPでロギング AOPの定番ネタですがロギングです。

まずは設定から

@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}

はい。EnableAspectJAutoProxyしてるだけです。

次にロギングのインターセプタを作ります。

@Component
@Aspect
public class LogInterceptor {
 
    private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
    @AfterReturning("execution(public * net.orekyuu.spring.service..*.*(..))")
    public void printLog(JoinPoint joinPoint) {
        logger.info("ServiceLog: " + joinPoint.getSignature());
    }
}

net.orekyuu.spring.serviceパッケージ以下にある全てのpublicなメソッドのどれかが正常終了した時にログを出力しています。 ポイントカット式とかの書き方は以下を見ればよいかと。 http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html

やりたい設計とか Controller→Service→Repositoryの構造になんか近いような・・・? EventListener→Service→Repositoryって結構よさ気では!?

テストとかDBに関してはまた後日