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とは親子関係がなくて見つからないみたいな流れ?
これ当たりっぽそう? pic.twitter.com/n7YiahyZ0Y
— 俺九番 (@orekyuu) 2016年9月9日
そのため解決策として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に関してはまた後日