Spring FrameworkでAOP(Aspect Oriented Programming)、とは関係ないカレントディレクトリ問題でハマる...

f:id:ts0818:20210603200727j:plain

www.itmedia.co.jp

 オランダProsusは6月2日(現地時間)、プログラマー向けQ&Aサイトを運営する米Stack Overflowを買収することで合意したと発表した。買収総額は約18億ドル(約1972億円)。取引は第3四半期(7~9月)に完了する見込みだ。

プログラマー向けQ&Aサイト「Stack Overflow」をProsusが18億ドルで買収 - ITmedia NEWS

 Stack Overflowは2008年創業のニューヨークに拠点を置く非公開企業。プログラミングについての質問と回答をユーザー登録なしで投稿できる。ユーザー投票によって有用な質問や回答が上位に表示される仕組みが特徴だ。月間訪問者数は1億人以上。2014年から日本語版βも提供している。

プログラマー向けQ&Aサイト「Stack Overflow」をProsusが18億ドルで買収 - ITmedia NEWS

⇧ ニューヨーク発祥だったとは知らなんだ、どうもボクです。

というわけで、今回もJavaフレームワークSpring Framework」なんかについてです。

レッツトライ~。

 

AOPAspect Oriented Programming)とは?

Wikipediaさんに聞いてみる。 

アスペクト指向プログラミングアスペクトしこうプログラミング、Aspect Oriented Programming、AOP)は、オブジェクト指向ではうまく分離できない特徴(クラス間を横断 (cross-cutting) するような機能)を「アスペクト」とみなし、アスペクト記述言語をもちいて分離して記述することでプログラムに柔軟性をもたせようとする試みで、極端に言えば、あるプログラムFの本質とは無関係だが使うかもしれないプログラムGを、プログラムFを実行したときに割り込ませるような機能である。

アスペクト指向プログラミング - Wikipedia

プログラムGこそがアスペクトであり、これを別ファイルに分離し、処理系がディスパッチすることで、プログラムF以外を実行するときにもプログラムGを割り込ませることができる。

アスペクト指向プログラミング - Wikipedia

アスペクトの例としては、データ転送帯域の制限や例外の処理などがある。Javaアスペクト指向的要素を追加したAspectJが実験的に実装されている。

アスペクト指向プログラミング - Wikipedia

⇧ 例えば、Javaとかで共通的な処理を実施したい箇所が100000000カ所ぐらいあったとして、そのすべてにハードコーディングするとなると、修正することになった時なんかが辛過ぎる...ということで、「AOPAspect Oriented Programming)」であれば、コーディングを1つにまとめられますよ、ということですかね。

イメージ的には、

www.dineshonjava.com

⇧「AOPAspect Oriented Programming)」使わない場合が、上図のような感じでコーディング量が多いのに対し、

⇧「AOPAspect Oriented Programming)」使った場合は、上図のような感じで3つコーディングすれば、あとは良しなに処理を差し挟んでくれると。

大規模な開発になってくるほど、ファイル数やコーディング量が増えていくことになると思われるので「AOPAspect Oriented Programming)」の効果が実感できることになるのではないかと。

 

Spring AOPAspectJの違いって?

で、Javaの「AOPAspect Oriented Programming)」 ライブラリとしては、

が有名らしいのですが、

pikipikipikky.hatenadiary.org

ここで疑問が生じた。プロキシを介してしか織り込めないのだとしたら、自クラスから呼び出す自クラス内のメソッドへは織り込むことができないのか?ということで実験実験・・・

SpringのAOPはAspectJとは似て非なり - 忘れたいことは忘れないこと

⇧ 上記サイト様によりますと、機能に差異があるらしいですと。

ただ、

www.baeldung.com

 

⇧「Spring AOP」に関して、アプローチが、 

 って2つあるみたいなんだけど、

codehero.jp

www.eisbahn.jp

⇧ 上記サイト様によりますと、「動的プロキシ」って仕組みを実現する際の実装の違いがあるってことらしい。

ちなみに、

qiita.com

⇧ 上記サイト様によりますと、「静的プロキシ」って仕組みもあるみたいね。 

ちなみに、

stackoverflow.com

we see that this is a class implementing an interface. The default for Spring AOP auto-proxying is that it uses JRE dynamic proxies, i.e. when creating the bean like this

Connector connector = ApplicationContextHolder.getBean("connector");

Spring will create a dynamic proxy implementing all interfaces the target class also implements. In this case this is Runnable only. I.e. the proxy object will only have methods from that interface and can only be cast to that interface. This explains why you cannot cast the proxy to Connector.

If you want Spring to create proxies for classes directly via CGLIB, you need to change your configuration in beans.xml to

<aop:aspectj-autoproxy proxy-target-class="true"/>

Then the server will start up normally because the CGLIB proxy is a direct Connector subclass, which also means that the assignment works as intended.

See the Spring manual for more information.

https://stackoverflow.com/questions/64098763/spring-aop-cannot-be-applied-to-class-loaded-by-custom-classloader

⇧ 上記サイト様によりますと、「Spring AOP」のデフォルトは「JRE dynamic proxies」を使ってるらしく、「CGLIB」を使いたい場合は、設定を記述する必要があるらしい。

というか、「JDK Proxy」「JDK動的プロキシ」「JRE dynamic proxies」って、みんな言い方が違うんだけど、同じものについて議論してるという認識で良いんかな?

ちなみに、

kamatama41.hatenablog.com

proxy-target-class="true"でCGLIBに強制した場合、実装クラスに対するAutowiredは上手くいくんですが
今度はProxyingの対象クラスが多いとOOMエラーになってしまうという現象も発生しました。
JDK〜の場合は起こらない。詳細は未調査)

SpringAOP(JDK Dynamic Proxy)のDI仕様にハマった - かまたま日記3

⇧ 上記サイト様によりますと、「プロキシ」させる対象のクラスが多い場合に「CGLIB」を使うと「OOMエラー」が起こるんだそうな。

「OOM」は、おそらく「OutOfMemory」のことを指しているかと。いや~、「OutOfMemory」が起こるのは厳しいですね...

 

実際に実装してみる

例のごとく、

github.com

⇧ 上記サイト様のお題を実施で。 

/hello および /goodbye の2つのパスにアクセスされた際に200 OKを返すサーバーを実装せよ。」ってことみたいなので、「REST」で実装すれば良さ気っぽいですね。

stackoverflow.com

⇧ 上記サイト様によりますと、 

  • spring-web
  • spring-webmvc

のどちらの依存関係でも「REST」は実現できるっぽいのですが、「XML(Extensible Markup Language)」の設定ファイルで「spring-web」の設定が分からんかったので、「spring-webmvc」を使っていくことにします。 

で、「AOPAspect Oriented Programming)」 ライブラリとしては、

note.com

⇧ 上記サイト様によりますと、

  • spring-aspects
  • spring-aop

の2つを依存関係に追加すれば良さ気ですと。 

www.shookuro.com

⇧ 上記サイト様によりますと、「Mavenプロジェクト」や「Gradleプロジェクト」で作り始めると相当に面倒くさいことになるみたいです。


まぁ、「Gradleプロジェクト」で作り始めてしまったんで、上記サイト様と同じように苦しんでいく感じになりますかね...

ここからは、「Gradleプロジェクト」や「Mavenプロジェクト」で無い場合は、読み飛ばしてもOK。

Eclipseの「パッケージ・エクスプローラー」上で対象のプロジェクトを選択した状態で右クリックし「プロパティー(R)」を選択。

f:id:ts0818:20210603145919p:plain

「プロジェクト・ファセット」の「ファセット・フォームへ変換...」をクリック。

f:id:ts0818:20210531145837p:plain

 「プロジェクト・ファセット」の「動的Webモジュール」にチェックを入れ、

f:id:ts0818:20210531145945p:plain

「適用して閉じる」をクリック。

f:id:ts0818:20210531150055p:plain

「WebContent」って「ディレクトリ」ができるっぽい。

f:id:ts0818:20210531150257p:plain

なんか、

spring.pleiades.io

⇧「web.xml」は必須らしい。

というわけで、「web.xml」を作成していきます。

「WEB-INF」フォルダを選択した状態で右クリックし、「新規(W)」>「ファイル」を選択。

f:id:ts0818:20210531152526p:plain

「ファイル名(M):」に「web.xml」と入力し、「完了(F)」を押下。

f:id:ts0818:20210603150601p:plain

「WEB-INF」直下に「web.xml」が作成されました。

f:id:ts0818:20210603150756p:plain

⇧ ファイルの中身が空なのでエラーが出てます。「web.xml」を編集で。 

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

  <!-- 文字のエンコーディングを指定し  -->
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>
      <!-- org.Apache.catalina.filters.SetCharacterEncodingFilter -->
      org.springframework.web.filter.CharacterEncodingFilter
    </filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>

  <!-- 上記フィルターをすべての URL で適用する。 -->
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- リスナーを登録 -->
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>

  <!-- spring.xml -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:/config/spring-web.xml</param-value>
  </context-param>

  <!-- Spring MVC アプリの場合、だいたいは唯一のサーブレットを登録する。 -->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value></param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- すべての URL リクエストについて、上記で登録したサーブレットで処理する。 -->
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

⇧ そうすると、エラーが消えます。

f:id:ts0818:20210603151141p:plain

 

「サーバー」タブで、「アプリケーションサーバー(ここでは、Tomcat)」を選択した状態で右クリックし、「追加および除去(A)...」を選択。

f:id:ts0818:20210531150633p:plain

「使用可能(A):」で、今回対象のプロジェクトを選択。「追加(D)>」を押下。 

f:id:ts0818:20210531150804p:plain

「構成済み(C):」に追加されてればOK。

f:id:ts0818:20210531150931p:plain

他のプロジェクトは除去しときます。

f:id:ts0818:20210531151524p:plain

今回対象のプロジェクトのみ「構成済み(C):」に配置されたら、「完了(F)」で。

f:id:ts0818:20210531151601p:plain

続きまして、

「パッケージ・エクスプローラー」で対象のプロジェクトを選択した状態で右クリックし、「プロパティー(R)」を選択。

f:id:ts0818:20210531160003p:plain

「デプロイメント・アセンブリー」で「追加(D)...」を押下。

f:id:ts0818:20210531160059p:plain

Java ビルド・パス・エントリー」を選択し、「次へ(N)>」を押下。

f:id:ts0818:20210531160159p:plain

「プロジェクトと外部の依存関係」を選択し、「完了(F)」を押下。

f:id:ts0818:20210531160236p:plain

そうすると、「サーバーで実行」が選択できるようになるようです。

f:id:ts0818:20210531151056p:plain

「完了(F)」を押下すると、「アプリケーションサーバー」が起動するっぽい。

f:id:ts0818:20210531151145p:plain 


さらに、ファイルパスの取得で悲報...

これ、Eclipse特有の問題なのか分からんのだけど、「カレントディレクトリ」のパスを取得する方法が、軒並み「ワークスペース」のことが考慮されていないという...

何を言ってるかというと、例えば、

stackoverflow.com

⇧ 上記サイト様を参考に、以下のように「C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\workspace\Spring_Framework_Aop\src\main\java\Spring_Framework_Aop\aop\SpringAspect.java」で「カレントディレクトリ」のパスを取得するコーディングを実装すると、

Path root =Paths.get(System.getProperty("user.dir"));    

な、何と!

C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\eclipse    

⇧ っていう結果が返ってくるというね...

いやいやいや、「SpringAspect.java」が配置されてる「ディレクトリ」って

C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\workspace\Spring_Framework_Aop\src\main\java\Spring_Framework_Aop\aop\

なんですけど...、何で、

\workspace\Spring_Framework_Aop\src\main\java\Spring_Framework_Aop\aop\

が無かったことにされてるの?

っていうか、「\eclipse」とか意味分からん「ディレクトリ」返されても困るんだが...

システムプロパティの一覧で「user.dir」も確認できるらしいので、確認。

Properties properties = System.getProperties();
Enumeration<Object> enumeration = properties.keys();
for (int i = 0; i < properties.size(); i++) {
    Object obj = enumeration.nextElement();
    System.out.println("Key: "+obj+"\tOutPut= "+System.getProperty(obj.toString()));
} 

⇧ 上記を実行した結果、

Key: sun.desktop	OutPut= windows
Key: awt.toolkit	OutPut= sun.awt.windows.WToolkit
Key: java.specification.version	OutPut= 11
Key: sun.cpu.isalist	OutPut= amd64
Key: sun.jnu.encoding	OutPut= MS932
Key: java.class.path	OutPut= C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\tomcat\9\bin\bootstrap.jar;C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\tomcat\9\bin\tomcat-juli.jar
Key: java.vm.vendor	OutPut= AdoptOpenJDK
Key: sun.arch.data.model	OutPut= 64
Key: user.variant	OutPut= 
Key: java.vendor.url	OutPut= https://adoptopenjdk.net/
Key: catalina.useNaming	OutPut= true
Key: user.timezone	OutPut= Asia/Tokyo
Key: os.name	OutPut= Windows 10
Key: java.vm.specification.version	OutPut= 11
Key: sun.java.launcher	OutPut= SUN_STANDARD
Key: user.country	OutPut= JP
Key: sun.boot.library.path	OutPut= C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\java\11\bin
Key: sun.java.command	OutPut= org.apache.catalina.startup.Bootstrap start
Key: jdk.debug	OutPut= release
Key: sun.cpu.endian	OutPut= little
Key: user.home	OutPut= C:\Users\Toshinobu
Key: user.language	OutPut= ja
Key: java.specification.vendor	OutPut= Oracle Corporation
Key: java.naming.factory.url.pkgs	OutPut= org.apache.naming
Key: java.version.date	OutPut= 2020-04-14
Key: java.home	OutPut= C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\java\11
Key: file.separator	OutPut= \
Key: java.vm.compressedOopsMode	OutPut= 32-bit
Key: line.separator	OutPut= 

Key: java.specification.name	OutPut= Java Platform API Specification
Key: java.vm.specification.vendor	OutPut= Oracle Corporation
Key: java.awt.graphicsenv	OutPut= sun.awt.Win32GraphicsEnvironment
Key: package.access	OutPut= sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat.
Key: package.definition	OutPut= sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.naming.,org.apache.tomcat.
Key: user.script	OutPut= 
Key: server.loader	OutPut= 
Key: sun.management.compiler	OutPut= HotSpot 64-Bit Tiered Compilers
Key: java.runtime.version	OutPut= 11.0.7+10
Key: java.naming.factory.initial	OutPut= org.apache.naming.java.javaURLContextFactory
Key: user.name	OutPut= Toshinobu
Key: path.separator	OutPut= ;
Key: common.loader	OutPut= "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
Key: os.version	OutPut= 10.0
Key: java.runtime.name	OutPut= OpenJDK Runtime Environment
Key: file.encoding	OutPut= UTF-8
Key: java.vm.name	OutPut= OpenJDK 64-Bit Server VM
Key: java.vendor.version	OutPut= AdoptOpenJDK
Key: java.vendor.url.bug	OutPut= https://github.com/AdoptOpenJDK/openjdk-support/issues
Key: java.io.tmpdir	OutPut= C:\Users\TOSHIN~1\AppData\Local\Temp\
Key: tomcat.util.scan.StandardJarScanFilter.jarsToScan	OutPut= log4j-taglib*.jar,log4j-web*.jar,log4javascript*.jar,slf4j-taglib*.jar
Key: catalina.home	OutPut= C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\tomcat\9
Key: java.version	OutPut= 11.0.7
Key: tomcat.util.scan.StandardJarScanFilter.jarsToSkip	OutPut= annotations-api.jar,ant-junit*.jar,ant-launcher.jar,ant.jar,asm-*.jar,aspectj*.jar,bootstrap.jar,catalina-ant.jar,catalina-ha.jar,catalina-ssi.jar,catalina-storeconfig.jar,catalina-tribes.jar,catalina.jar,cglib-*.jar,cobertura-*.jar,commons-beanutils*.jar,commons-codec*.jar,commons-collections*.jar,commons-daemon.jar,commons-dbcp*.jar,commons-digester*.jar,commons-fileupload*.jar,commons-httpclient*.jar,commons-io*.jar,commons-lang*.jar,commons-logging*.jar,commons-math*.jar,commons-pool*.jar,dom4j-*.jar,easymock-*.jar,ecj-*.jar,el-api.jar,geronimo-spec-jaxrpc*.jar,h2*.jar,hamcrest-*.jar,hibernate*.jar,httpclient*.jar,icu4j-*.jar,jasper-el.jar,jasper.jar,jaspic-api.jar,jaxb-*.jar,jaxen-*.jar,jdom-*.jar,jetty-*.jar,jmx-tools.jar,jmx.jar,jsp-api.jar,jstl.jar,jta*.jar,junit-*.jar,junit.jar,log4j*.jar,mail*.jar,objenesis-*.jar,oraclepki.jar,oro-*.jar,servlet-api-*.jar,servlet-api.jar,slf4j*.jar,taglibs-standard-spec-*.jar,tagsoup-*.jar,tomcat-api.jar,tomcat-coyote.jar,tomcat-dbcp.jar,tomcat-i18n-*.jar,tomcat-jdbc.jar,tomcat-jni.jar,tomcat-juli-adapters.jar,tomcat-juli.jar,tomcat-util-scan.jar,tomcat-util.jar,tomcat-websocket.jar,tools.jar,websocket-api.jar,wsdl4j*.jar,xercesImpl.jar,xml-apis.jar,xmlParserAPIs-*.jar,xmlParserAPIs.jar,xom-*.jar
Key: user.dir	OutPut= C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\eclipse
Key: os.arch	OutPut= amd64
Key: java.vm.specification.name	OutPut= Java Virtual Machine Specification
Key: java.awt.printerjob	OutPut= sun.awt.windows.WPrinterJob
Key: sun.os.patch.level	OutPut= 
Key: catalina.base	OutPut= C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0
Key: shared.loader	OutPut= 
Key: wtp.deploy	OutPut= C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps
Key: java.library.path	OutPut= C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\java\11\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:/Eclipse-2020-06/pleiades-2020-06-java-win-64bit-jre_20200702/pleiades/eclipse//jre/bin/server;C:/Eclipse-2020-06/pleiades-2020-06-java-win-64bit-jre_20200702/pleiades/eclipse//jre/bin;C:\python2.7.16\;C:\python2.7.16\Scripts;C:\app02\oracle\product\19.0.0\dbhome_1\bin;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Python37\Scripts\;C:\Python37\;C:\app\product\12.2.0\dbhome_1\bin;C:\Program Files\Common Files\Microsoft Shared\Windows Live;C:\Program Files (x86)\Common Files\Microsoft Shared\Windows Live;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Windows Live\Shared;C:\Program Files\Intel\WiFi\bin\;C:\Program Files\Common Files\Intel\WirelessCommon;c:\xampp\perl\bin\;c:\xampp\php\;c:\xampp\mysql\bin\;C:\ProgramData\ComposerSetup\bin;C:\ProgramData\chocolatey\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Skype\Phone\;C:\Users\Toshinobu\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\OpenJDK\jdk-16.0.1\bin;C:\Program Files\ISC BIND 9\bin;C:\Program Files\PostgreSQL\9.6\bin;C:\Program Files\Oracle\VirtualBox;C:\msys64\usr\bin;C:\Program Files\Docker Toolbox;C:\nginx-1.12.0;C:\Program Files\MySQL\MySQL Utilities 1.6\;C:\Program Files (x86)\GtkSharp\2.12\bin;C:\Program Files\Cloud Foundry;C:\spring-2.0.0.M5\bin;C:\Program Files\PuTTY\;C:\Program Files\TortoiseSVN\bin;C:\Program Files\kubectl;C:\Program Files\minikube;C:\Program Files\Microsoft VS Code\bin;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\OpenSSH-Win64;C:\Users\Toshinobu\AppData\Roaming\nvm;C:\nodejs;C:\Program Files (x86)\DevDesktop\tools;C:\chocolatey-package\php73;C:\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit\;C:\Program Files\TortoiseGit\bin;C:\ProgramData\chocolatey\lib\maven\apache-maven-3.6.2\bin;C:\Go\bin;C:\Program Files (x86)\sbt\bin;C:\Program Files (x86)\Subversion\bin;C:\HashiCorp\Vagrant\bin;C:\Users\Toshinobu\bin;C:\Program Files\Git\cmd;C:\Program Files\Docker\Docker\resources\bin;C:\ProgramData\DockerDesktop\version-bin;C:\Ruby24-x64\bin;C:\Program Files\Common Files\Microsoft Shared\Windows Live;C:\Program Files (x86)\Common Files\Microsoft Shared\Windows Live;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Windows Live\Shared;C:\Program Files\Intel\WiFi\bin\;C:\Program Files\Common Files\Intel\WirelessCommon;c:\xampp\perl\bin\;c:\xampp\php\;c:\xampp\mysql\bin\;C:\ProgramData\ComposerSetup\bin;C:\ProgramData\chocolatey\bin;C:\Program Files\TortoiseGit\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Program Files\nodejs\;C:\Program Files (x86)\Skype\Phone\;C:\WINDOWS\system32\config\systemprofile\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files (x86)\apache-maven-3.3.9\bin;C:\Program Files\Java\jdk1.8.0_112\bin;C:\ProgramData\Oracle\Java\javapath;C:\msys64\usr;C:\Users\Toshinobu\AppData\Local\Microsoft\WindowsApps;C:\Program Files\Microsoft VS Code\bin;C:\Users\Toshinobu\AppData\Roaming\nvm;C:\nodejs;C:\go\bin;C:\Users\Toshinobu\go\bin;C:\Program Files\LibreOffice\program;C:\Program Files\LibreOffice\sdk\lib;C:\Users\Toshinobu\go\bin;;C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\eclipse;;.
Key: java.vendor	OutPut= AdoptOpenJDK
Key: java.vm.info	OutPut= mixed mode
Key: java.vm.version	OutPut= 11.0.7+10
Key: sun.io.unicode.encoding	OutPut= UnicodeLittle
Key: tomcat.util.buf.StringCache.byte.enabled	OutPut= true
Key: java.class.version	OutPut= 55.0 

⇧ ってな感じで、「user.dir」の値は、

C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\eclipse    

⇧ ってなってました...

いや~、Javaカオス過ぎる...、っていうか、環境に依存するって駄目でしょ...

「開発環境」と「本番環境」で挙動が変わってきたりしたらアウトなんだが...

『Write once, run anywhere』にならないじゃん... 

試しに、「Spring Framework」で用意されてる「ResourceUtils」ってので、「ディレクトリのパス」を取得してみたところ、

ResourceUtils.getURL("classpath:").getPath();

衝撃の結果が...

/C:/Eclipse-2020-06/pleiades-2020-06-java-win-64bit-jre_20200702/pleiades/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/Spring_Framework_Aop/WEB-INF/classes/

⇧ え...なんか更にパスが変わったんだけど...

いやはや、普通に「src/main/resources」までの「ディレクトリのパス」を取得したいだけなんですが...

そして、さらなる悲報...

docs.oracle.com

⇧ まさかのOracleさんが、フルパスをハードコーディングしてるというね...

それって、つまり「開発環境」と「本番環境」でソースコードを書き替えないといけないってシチュエーション発生してしまうんでないの?

いや~、残念過ぎる...

っていうか、Javaのファイル操作のAPIって仕様が破綻している気がしてならない...

あとは、

www.techscore.com

⇧「ビルドツール」依存になってしまうけども、「Gradle」を使ってる場合は、「プロジェクトルート」のパスが取得できるっぽいので、「Gradle」側で値を取得して、Java側に値を渡してあげるって方法か、

yukihane.github.io

⇧ 上記サイト様によりますと、「ファイル出力先」で使うのはNGっぽいですが、既存の「ディレクトリ」のパスを取得する分には問題ないかと思われるので、「ServletContext」経由でパスが取得できるっぽいですかね。

というか、Javaで「ディレクトリのパス」を取得するのって、こんなに面倒くさいってのが衝撃的過ぎるんだけど...

というわけで、「Gradle」のプロパティファイル経由で「プロジェクトルート」 のパスを取得することにしようと思って、

www.atmarkit.co.jp

⇧ 上記サイト様を参考に、 

f:id:ts0818:20210602172412p:plain

⇧「プロパティファイル」を配置してJava側でファイルを読み込もうとするも、「java.io.FileNotFoundException」が発生するんだけど...

そもそもとして、

teratail.com

Eclipseからクラスを実行した場合、カレントディレクトリはプロジェクトがあるディレクトリです。

例えば プロジェクトが /eclipse/workspace/sample_project にある場合、

String path = Paths.get(".").toFile().getAbsolutePath();

実行すると、

/eclipse/workspace/sample_project/.

が返されます。

ファイルパスの先頭にスラッシュを記載するとルートディレクトリからのファイルパスを示します。

Java - Paths.get とカレントディレクトリ について|teratail

⇧ という情報があったんですが、自分の環境では、

String path = Paths.get(".").toFile().getAbsolutePath();
System.out.println(path);

⇧ を実行したところ、

C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\eclipse\. 

f:id:ts0818:20210603131432p:plain

f:id:ts0818:20210603131501p:plain

ちなみに、プロジェクトがあるディレクトリは、 

C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\workspace\Spring_Framework_Aop    

⇧ ってな感じになってるんですが...

Eclipseからクラスを実行した場合、カレントディレクトリはプロジェクトがあるディレクトリです。』って言葉をどう解釈すれば良いのか...

そして、衝撃...

rebis.jugem.jp

Javaサーブレットなどからファイル操作を行う場合、初期設定だとパスの指定が面倒だ。

Eclipse+Tomcatにおけるカレントディレクトリ | 最早、愛など諦めた。

⇧ 上記サイト様によりますと、 「Servlet」使ってる場合は、「カレントディレクトリ」が変わってくるらしい...まじか...

Javaさん、環境依存しまくりやん...

というわけで、Eclipseの「サーバー」タブに表示されてる「アプリケーションサーバー(赤枠で囲ってる部分)」をダブルクリック。

f:id:ts0818:20210603133748p:plain

設定ファイルが開くので、「起動構成を開く」をクリック。

f:id:ts0818:20210603134001p:plain

「引数」タブを選択し、「作業ディレクトリー」があるので、編集すれば良さ気。

ようやく謎が解けたよ...

正解の情報に辿り着くために1週間ぐらい無駄にしたんだけど、どうしてくれよう...

f:id:ts0818:20210603134218p:plain

ワークスペースでなくとも、ファイルシステムでも指定できる。
プロジェクトのあるディレクトリを指定したいなら「ワークスペース
その他の任意のディレクトリを指定したいなら「ファイル・システム」をクリックすれば良いと思う。

Eclipse+Tomcatにおけるカレントディレクトリ | 最早、愛など諦めた。

⇧ ということらしいので、「ワークスペース」を選択しときますか。

f:id:ts0818:20210603134650p:plain

対象のプロジェクトを選択して、「OK」で。

f:id:ts0818:20210603134737p:plain

「OK」で。

f:id:ts0818:20210603134931p:plain

 

で、改めて、Eclipse内蔵の「アプリケーションサーバー」で「デバッグ実行」を実施してみたところ、

String path = Paths.get(".").toFile().getAbsolutePath();
System.out.println(path);

以下のようなパスが表示されました。

C:\Eclipse-2020-06\pleiades-2020-06-java-win-64bit-jre_20200702\pleiades\workspace\Spring_Framework_Aop\.

⇧ 今度は無事に、「プロジェクトディレクトリ」までのパスが取得できました。

というか、「本番環境」だとどういう設定になってるのかな?

そして、さらなる残念過ぎる情報が見つかる...

blogs.osdn.jp

Java 11  user.dir システムプロパティを書き換えることができなくなり それによって 実行時に相対パス解決の開始点を変更することもできなくなってしまいました。

Javaはカレントディレクトリを使わない

厳密に言うと Java 11 でも user.dir システムプロパティの書き換え自体はできます。しかし JavaVM の起動時にファイル API 群が user.dir システムプロパティの値のコピーを保持するように変更されたため 実行時に user.dir システムプロパティを書き換えても 相対パス解決の開始点として反映されなくなりました。

Javaはカレントディレクトリを使わない

この変更に対して バグレポートがあがっていました。

報告者は Java 11  user.dir システムプロパティを変更しても相対パス解決に反映されなくなった ことを説明しています。

Javaはカレントディレクトリを使わない

このバグレポートは冷たい回答とともにクローズされてしまいました。

System クラスの Javadoc にある注釈を読んでください。

標準のシステムプロパティを変更すると予測できない結果になるかもしれません。プロパティの値は初期化や初回使用時にキャッシュされます。その後にシステムプロパティを変更しても望んだ効果を得られないかもしれません。

これはバグではありません。VM 実行中に user.dir プロパティを決して変更すべきではありません。

Javaはカレントディレクトリを使わない

Javadoc で注意喚起されていたのなら仕方がないか…。
そう思いつつ 念のために Javadoc に目を通してみました。

Javaはカレントディレクトリを使わない

あれ? Java 10 までの Javadoc にはそんな記述はありません。Java 11 で動作が変更され それに合わせて Javadoc にも注意が追記されたようです。

Javaはカレントディレクトリを使わない

⇧ お、終わってる...

『誠実さのかけらもなく 笑っている奴がいるよ(「青空 THE BLUE HEARTS」)』みたいな所業、まさに、ゲスの極み!

粗を探したいわけじゃないのに、次から次へとイケてない部分ばかり見つかり、正直なところ、Javaに対しての失望感たるや、筆舌に尽くしがたいと言わざるを得ません...

いや~、本当にJavaのバージョンを上げるのは構わんのだけど、バージョンアップを慌ただしくすることよりも大事なことあると思うけどな...

 

はい、脱線しました。

ようやく、本題の「Spring Framework」で「AOPAspect Oriented Programming)」を実装で。

ディレクトリの構成とかは以下のような感じになりました。

f:id:ts0818:20210603171711p:plain

では、各ファイルを編集で。 

■/Spring_Framework_Aop/build.gradle

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java Library project to get you started.
 * For more details take a look at the Java Libraries chapter in the Gradle
 * User Manual available at https://docs.gradle.org/6.3/userguide/java_library_plugin.html
 */

plugins {
    // Apply the java-library plugin to add support for Java Library
    id 'java-library'
}

repositories {
    // Use jcenter for resolving dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

dependencies {
    // This dependency is exported to consumers, that is to say found on their compile classpath.
    api 'org.apache.commons:commons-math3:3.6.1'

    // This dependency is used internally, and not exposed to consumers on their own compile classpath.
    implementation 'com.google.guava:guava:28.2-jre'

	// https://mvnrepository.com/artifact/org.springframework/spring-core
	implementation group: 'org.springframework', name: 'spring-core', version: '5.3.7'

	// https://mvnrepository.com/artifact/org.springframework/spring-context
	implementation group: 'org.springframework', name: 'spring-context', version: '5.3.7'

	// https://mvnrepository.com/artifact/org.springframework/spring-aspects
	implementation group: 'org.springframework', name: 'spring-aspects', version: '5.3.7'

	// https://mvnrepository.com/artifact/org.springframework/spring-aop
	implementation group: 'org.springframework', name: 'spring-aop', version: '5.3.7'

    // https://mvnrepository.com/artifact/org.springframework/spring-webmvc
    implementation group: 'org.springframework', name: 'spring-webmvc', version: '5.3.7'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'
}

⇧依存関係としては、「spring-core」「spring-context」「spring-aspects」「spring-aop」「spring-webmvc」

■/Spring_Framework_Aop/gradle.properties

projectDir = ${projectDir}    

■/Spring_Framework_Aop/src/main/resources/config/spring-web.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- Spring MVC の機能を使うことを宣言。 -->
	<!-- この宣言をすることで、 @Component などのアノテーションが使えるようになる。 -->
	<mvc:annotation-driven />
	<!-- <context:annotation-config />-->
	<!-- AspectJスタイルのSpring AOPを有効化 -->
	<aop:aspectj-autoproxy/>
	<!-- Bean となるクラスファイルが格納されているパッケージを宣言。 -->
	<!-- Spring はこのパッケージ配下を自動でスキャンし、Bean として登録する。 -->
	<context:component-scan base-package="Spring_Framework_Aop.*" />

</beans>    

■/Spring_Framework_Aop/WebContent/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

  <!-- 文字のエンコーディングを指定し  -->
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>
      <!-- org.Apache.catalina.filters.SetCharacterEncodingFilter -->
      org.springframework.web.filter.CharacterEncodingFilter
    </filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>

  <!-- 上記フィルターをすべての URL で適用する。 -->
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- リスナーを登録 -->
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>

  <!-- spring.xml -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:/config/spring-web.xml</param-value>
  </context-param>

  <!-- Spring MVC アプリの場合、だいたいは唯一のサーブレットを登録する。 -->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value></param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- すべての URL リクエストについて、上記で登録したサーブレットで処理する。 -->
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>    

■/Spring_Framework_Aop/src/main/java/Spring_Framework_Aop/controller/HelloController.java

package Spring_Framework_Aop.controller;

import java.io.FileNotFoundException;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

  @RequestMapping(value="/", method = RequestMethod.GET)
  public String index() {
    String str;
    try {
      str = ResourceUtils.getURL("file:/Spring_Framework_Aop/src/main/resources").getPath();
      System.out.println(str);
    } catch (FileNotFoundException e) {
      // TODO 自動生成された catch ブロック
      e.printStackTrace();
    }

    return "うぇるかむ!!";
  }

  @RequestMapping(value="/hello", method= RequestMethod.GET)
  public String hello() {
    return handleNotifications().getStatusCode().toString();
  }

  @RequestMapping(value="/goodbye", method=RequestMethod.GET)
  public String goodbye() {
    return handleNotifications().getStatusCode().toString();
  }

  // "200 OK" を返す
  private ResponseEntity<String> handleNotifications () {
    return new ResponseEntity<>("", HttpStatus.OK);

  }
}

■/Spring_Framework_Aop/src/main/java/Spring_Framework_Aop/aop/SpringAspect.java

package Spring_Framework_Aop.aop;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "prototype")
@Aspect
@Component
public class SpringAspect {

  //private static final String PROJECT_DIR = "Spring_Framework_Aop";
  private static final String LOG_DIR_PATH = "src/main/resources/log";
  private static final String LOG_FILE_NAME = "access.log";

  @Before("execution(* Spring_Framework_Aop.controller.*.*(..))")
  public void requestMappingGet() {
    System.out.println("[AOP start]");
//    Properties properties = System.getProperties();
//    Enumeration<Object> enumeration = properties.keys();
//    for (int i = 0; i < properties.size(); i++) {
//        Object obj = enumeration.nextElement();
//        System.out.println("Key: "+obj+"\tOutPut= "+System.getProperty(obj.toString()));
//    }
    //Path root = Paths.get("").toAbsolutePath();
    //Path root =Paths.get(System.getProperty("user.dir"));
    Path root = Paths.get(new File(".").getAbsoluteFile().getParent());
    //Path root = Paths.get(new File("." + File.pathSeparator+File.pathSeparator+"Spring_Framework_Aop").getAbsolutePath());
    //Path root = Paths.get(".").normalize().toAbsolutePath();
    //String path = Paths.get(".").toFile().getAbsolutePath();
    //System.out.println(path);
    try(InputStream input = new FileInputStream("gradle.properties");) {
      //String root = ResourceUtils.getURL("file:/Spring_Framework_Aop/src/main/resources").getPath();

      Properties prop = new Properties();
      prop.load(input);
      System.out.println(prop.getProperty("projectDir"));

      InetAddress addr = InetAddress.getLocalHost();
      Path dir = Paths.get(root.toString(), LOG_DIR_PATH);
      if (Files.notExists(dir)) {
        Files.createDirectories(dir);
      }
      Path filePath = Paths.get(dir.toString(), LOG_FILE_NAME) ;
      if (Files.notExists(filePath)) {
        Files.createFile(filePath);
      }
      try(BufferedWriter writer = Files.newBufferedWriter(filePath)) {
        writer.append(addr.getHostAddress());
      }

    } catch (UnknownHostException e) {
      // TODO 自動生成された catch ブロック
      e.printStackTrace();
    } catch (IOException e) {
      // TODO 自動生成された catch ブロック
      e.printStackTrace();
    }
  }

//  private static ServletContext getContext() {
//      return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()
//                  .getServletContext();
//  }
}

⇩ ってな感じの内容で保存。

そしたらば、「サーバー」タブで「アプリケーションサーバー」を選択した状態で右クリックし、「開始(S)」で。

f:id:ts0818:20210603181242p:plain

f:id:ts0818:20210603181410p:plain

f:id:ts0818:20210603181434p:plain

そしたらば、ブラウザから「http://localhost:8080/プロジェクト名/RequestMappingで指定したパス」 にアクセス。

f:id:ts0818:20210603181517p:plain

f:id:ts0818:20210603181550p:plain

f:id:ts0818:20210603181621p:plain

⇧ 無事、結果が表示されました。

確認できたら、「アプリケーションサーバー」を停止で。

f:id:ts0818:20210603181735p:plain

f:id:ts0818:20210603181801p:plain

「src/main/resources」ディレクトリを選択した状態で、「F5」キーを押下すると、

f:id:ts0818:20210603182000p:plain

「/Spring_Framework_Aop/src/main/resources/log/access.log」が作成されてるかと。

f:id:ts0818:20210603182120p:plain

access.log」には取得したIPアドレスが記載されてればOK。

f:id:ts0818:20210603182231p:plain

日本語が文字化けしてるのと、「gradle.properties」で変数展開させる方法が未解決だけども、一応「AOPAspect Oriented Programming)」できましたね。

この度もモヤモヤ感が半端ないのだが...

今回はこのへんで。