atomで設定を分けて起動する方法

Atom をメインで使っているとソースを書くために使ったりマークダウンエディタとして使ったりします。そのとき、パッケージをまとめて1つの設定に入れても良いんですが、色々な言語のautocomplate系を入れるのが嫌なときやマークダウンエディタとして使いたいときに設定を分けて起動することができます。

ここからはmacをベースにします。

atomの設定は~/.atomにあります。これを環境変数ATOM_HOMEで指定することで設定を分けることができます。環境変数を指定してatomコマンドをこんな感じで呼んでやればATOM_HOMEで指定した設定で起動します。

ATOM_HOME=~/.atom_markdown atom

このコマンドを.bash_profileエイリアスで登録しておくと捗ります。

alias atommd='ATOM_HOME=~/.atom_markdown atom'

.bash_profileを読み込んだら以下のコマンドを実行すると~/.atom_markdownの設定でatomが起動します。

$ atommd .

Eclipse環境での開発サポート

一人で開発している分にはあまり気になりませんが複数人で開発するときに書き方がバラバラだったり、レビューが大変だったりします。コード規約を作っても読まなければならず徹底するのは大変です。 そこで、Eclipseで利用できる開発サポートツールをいくつか紹介します。

Checkstyle

まずは、Checkstyleです。これはPleiadesを使っていればバンドルされています。Checkstyleは静的コード解析ツールでルール通りに記載されているかを自動でチェックしてくれるツールです。デフォルトでは、google_checks.xmlが有効になっています。自分好みの設定をすることも可能です。ちなみに僕はタブ区切りでも良いようにルールを変更しています。

Findbugs

FindbugsPleiadesにバンドルされています。これもCheckstyleと同様、静的コード解析ツールですがこちらはバグになりそうな可能性のあるコードを見つけてくれます。

Eclipse Metrics

あと個人的には、Eclipse Metricsというものを入れています。これは、ソースの複雑度(クラスやメソッドの行数、ネストの深さなど)を計測できるツールです。警告されれば、クラスの責務やメソッドの凝集度などを考える機会になります。 Pleiadesにはバンドルされていなかったので以下のページを参考にインストールしました。

www.ibm.com

これらを使わなくても開発は可能です。ですが、あった方が保守しやすいコードが書けるかと思います(何ヶ月か前に書いた自分のコードはもはや他人のコードです)。課題としては、設定内容をファイル化してバージョン管理できるようにすれば変更を追跡できて、他の人の環境にも容易に反映することができます。 これらをほぼ網羅でき設定ファイルの共有も楽なRubyGemsrubocopは素晴らしいですね。

Spring Bootでoauthのログインセッションをredisに突っ込む

タイトル通りです。 前にoauth認証でのログインができるようにしましたが、これだとログインセッションのあるサーバでしかログイン状態を維持できません。これだとスケールしないので外部に保存します。そこでredisを使ってスケールする仕組みを作りたいと思います。

主に参考にさせてもらったのはこちら。

qiita.com

ただ、oauth認証のコードをベースにしていると以下のようなエラーが出ました。

2017-10-18 21:32:50.838 ERROR 52558 --- [io-8080-exec-10] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

org.springframework.data.redis.serializer.SerializationException: Could not write JSON: Error creating bean with name 'scopedTarget.accessTokenRequest': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. (through reference chain: org.springframework.security.oauth2.client.DefaultOAuth2ClientContext["accessTokenRequest"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Error creating bean with name 'scopedTarget.accessTokenRequest': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. (through reference chain: org.springframework.security.oauth2.client.DefaultOAuth2ClientContext["accessTokenRequest"])
    at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.serialize(GenericJackson2JsonRedisSerializer.java:99) ~[spring-data-redis-1.8.7.RELEASE.jar:na]
    at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:171) ~[spring-data-redis-1.8.7.RELEASE.jar:na]
    at org.springframework.data.redis.core.DefaultHashOperations.putAll(DefaultHashOperations.java:129) ~[spring-data-redis-1.8.7.RELEASE.jar:na]
    at org.springframework.data.redis.core.DefaultBoundHashOperations.putAll(DefaultBoundHashOperations.java:86) ~[spring-data-redis-1.8.7.RELEASE.jar:na]
    at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.saveDelta(RedisOperationsSessionRepository.java:778) ~[spring-session-1.3.1.RELEASE.jar:na]
    at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.access$000(RedisOperationsSessionRepository.java:670) ~[spring-session-1.3.1.RELEASE.jar:na]
    at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:388) ~[spring-session-1.3.1.RELEASE.jar:na]
    at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:245) ~[spring-session-1.3.1.RELEASE.jar:na]
    at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:245) ~[spring-session-1.3.1.RELEASE.jar:na]
    at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access$100(SessionRepositoryFilter.java:217) ~[spring-session-1.3.1.RELEASE.jar:na]
    at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:170) ~[spring-session-1.3.1.RELEASE.jar:na]
    at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80) ~[spring-session-1.3.1.RELEASE.jar:na]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) ~[spring-web-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) ~[spring-boot-actuator-1.5.7.RELEASE.jar:1.5.7.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1457) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_121]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_121]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.20.jar:8.5.20]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Error creating bean with name 'scopedTarget.accessTokenRequest': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. (through reference chain: org.springframework.security.oauth2.client.DefaultOAuth2ClientContext["accessTokenRequest"])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:388) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:348) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:343) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:697) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeWithType(BeanSerializerBase.java:580) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer.serialize(TypeWrappedSerializer.java:32) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3697) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsBytes(ObjectMapper.java:3097) ~[jackson-databind-2.8.10.jar:2.8.10]
    at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.serialize(GenericJackson2JsonRedisSerializer.java:97) ~[spring-data-redis-1.8.7.RELEASE.jar:na]
    ... 37 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.accessTokenRequest': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:355) ~[spring-beans-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:192) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at com.sun.proxy.$Proxy94.isEmpty(Unknown Source) ~[na:na]
    at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeWithType(MapSerializer.java:550) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeWithType(MapSerializer.java:30) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:706) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689) ~[jackson-databind-2.8.10.jar:2.8.10]
    ... 43 common frames omitted
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41) ~[spring-web-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340) ~[spring-beans-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    ... 51 common frames omitted

検索してもドンピシャな記事がなく色々と試行錯誤した結果以下のページに行き着きました。 以下のページを参考にRedisTemplate を作成しました。合わせてHttpSessionConfigクラスを削除すればredisに登録することができます。

Spring Boot Redis custom json serializer example. · GitHub

実装した結果はこちらです。

github.com

Spring Bootでoauth認証を使ってログインする

Webアプリケーションを作る上でなくてはならない機能の一つであるログイン機能。しっかり作ろうとすると色々と考えないといけないことが多かったりしてスクラッチで作るのは大変です。しかもID、PWを各サイトごとに覚えておかなければいけないのはユーザにとっては負担です。何ヶ月か使わなかっただけでパスワードが分からなくなってリセットしたりします。

そこで、oauthを使うことで覚えなければいけないID、PWを減らすことができてパスワード漏洩のリスクもゼロにできます。(ユーザ情報を保存する場合は漏洩のリスクはゼロではありませんが) ※そもそもoauthは認証ではなく認可だということは承知の上です。

基本的にはこちらを参考にさせてもらいました。

github.com

実装した結果はこちらです。

github.com

Spring Boot with doma

ORマッパとかDBアクセスフレームワークSQL書きたい派です。なので以前、仕事で使っていたMyBatis(当時はiBatis)は結構好きな方でした。その後、railsをメインで触るようになり、SQLを書く機会がめっきり減りました。railsくらいしっかりしたサポートがあればSQLを書かなくても良いのですが(SQL書いた方が早いだろってときもありますが)、SQLで書いた方が表現力は高いのでやっぱりSQLが書きたいということで前回に引き続きSpring Bootと組み合わせて使えるdomaを導入してみようと思います。

基本的にはこちらを参考にさせてもらいました。

github.com

以下のようなエラーが出たらMavengenerate-sourcesを実行してみてください。それでもよくならなければ一回cleanをして再度、generate-sourcesを実行することで上手くいくはず。その後、installを実行するとtargetディレクトリの下に〜daoImpl.javaができているはずなのでSpringBootアプリケーションを起動します。実際のところgenerate-sourcesを実行せずにinstallでも上手くいくかも(未検証)。

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [クラス名] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

実装した結果はこちらです。

github.com