Java リフレクションを使って同じメソッド名があるクラスを指定して実行する

リフレクションは昔、JDBCのドライバーを使う時、データベース別にクラスを違える為、class.forName() を使ったくらいで、その便利さが活用できぬままでしたが、同じメソッド名で異なる処理してるクラスを選んでインスタンス化 => メソッド実行とかできると楽で便利なので、ちょっと調べて実装してみました。

< コード例 >


package mapaddress;

import java.util.ArrayList;
import java.io.File;
import java.io.*;

import java.util.*;
import java.lang.reflect.Method;
import java.lang.reflect.Executable;
import java.lang.reflect.AccessibleObject;

public class MapAddress {

  public static void main(String[] args) {

    // 引数がない場合、すぐ終わらす //
    if (args.length == 0) {
      System.out.println("no argument");
      return;
    }

    // このクラスのパッケージ名 //
    String packageName = MapAddress.class.getPackage().getName();
    System.out.println(packageName);

    // 引数はクラス名の1個 //
    String nmClass = args[0];
    System.out.println(args[0]);

    MapAddress MA = new MapAddress();

    try {
      // クラスオブジェクト取得 //
      Class<?> c = Class.forName(packageName + "." + nmClass);

      // インスタンス化 //
      Object objc = c.newInstance();

      // 第一メソッド実行 //
      Method method = c.getMethod("getAddressPage");
      method.invoke(objc);

      // 第ニメソッド実行 //
      if (MA.is2methodMap.get(nmClass)) {
        method = c.getMethod("addStoreDetails");
        method.invoke(objc);
      }

      // 生成されたリストをデバッグ //
      method = c.getMethod("getStoreDtlList");
      System.out.println(method.invoke(objc));

      // ???????? なので各クラスのメソッドにする //
      //ArrayList<HashMap<String, String>> dtllist = method.invoke(objc);
      
      // 引数がある場合は、getMethodで定義しておく //
      method = c.getMethod("execUpdateTable", String.class, String.class);
      method.invoke(objc, MA.listnameMap.get(nmClass), iconfnm);
      

    }
    catch (Exception e) {
      e.printStackTrace();
    }

  }
  
}


ここらで教えて頂く :

https://www.sejuku.net/blog/33252

https://itsakura.com/java-reflect

http://pppurple.hatenablog.com/entry/2016/07/23/205446

https://www.logicbig.com/how-to/code-snippets/jcode-reflection-class-getmethod.html

GoogleMap API ジオコーダーを Java で使う

Javascript からの Geocorder の利用が、1回で連続して変換できるのが 10件 に制限されているので、大量データの変換に不向きなので、調べて実装しました。



package mapaddress.dbupdate;

import java.util.*;
import java.io.*;

import com.google.maps.GeoApiContext;
import com.google.maps.*;
import com.google.maps.model.GeocodingResult;
import com.google.maps.model.LatLng;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import org.apache.ibatis.session.SqlSession;

//import com.google.maps.GeoApiContextBuilder;
/**
 * GoogleMap API ジオコーダー用クラス
 */
public class Geocorder {

  /**
   * プロキシ設定
   */
  private final String PROXY_HOST = "?????????";
  private final int PROXY_PORT = 0;

  private final String API_KEY = "???????????????????????????????";

  private GeoApiContext context;

  private String addr;
  private double lat;
  private double lon;
  private String zip = "";

  /**
   * デフォルトコンストラクタ
   */
  public Geocorder() {
    context = new GeoApiContext.Builder()
            .apiKey(API_KEY)
            .build();
  }

  /**
   * APIキー指定コンストラクタ
   */
  public Geocorder(String apikey) {
    context = new GeoApiContext.Builder()
            .apiKey(apikey)
            .build();
  }

  /**
   * プロキシ用コンストラクタ
   *
   * @param isUseProxy
   */
  public Geocorder(boolean isUseProxy) {

    if (isUseProxy) {
      SocketAddress addr = new InetSocketAddress(this.PROXY_HOST, this.PROXY_PORT);
      Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);
      context = new GeoApiContext.Builder()
              .apiKey(API_KEY)
              .proxy(proxy)
              .build();
    }
    else {
      context = new GeoApiContext.Builder()
              .apiKey("????????????????????????????????????????")
              .build();
    }
  }

  /**
   * @return the addr
   */
  public String getAddr() {
    return addr;
  }

  /**
   * @param addr the addr to set
   */
  public void setAddr(String addr) {
    this.addr = addr;
  }

  /**
   * @return the lat
   */
  public double getLat() {
    return lat;
  }

  /**
   * @param lat the lat to set
   */
  public void setLat(double lat) {
    this.lat = lat;
  }

  /**
   * @return the lon
   */
  public double getLon() {
    return lon;
  }

  /**
   * @param lon the lon to set
   */
  public void setLon(double lon) {
    this.lon = lon;
  }

  /**
   * @return the zip
   */
  public String getZip() {
    return zip;
  }

  /**
   * @param zip the zip to set
   */
  public void setZip(String zip) {
    this.zip = zip;
  }

  /**
   * 正ジオコーダーの実行
   */
  public void execGeoCorder() {

    try {
      GeocodingResult results[] = this.getResults(this.addr);
      //LatLng latLng = results[0].geometry.location; // とりあえず一番上のデータを使う

      if (results != null && results.length > 0) {
        //if (results.length > 0) {
        LatLng latLng = results[0].geometry.location; // とりあえず一番上のデータを使う
        System.out.println("緯度 : " + latLng.lat);
        System.out.println("経度 : " + latLng.lng);
        int len = results[0].addressComponents.length;
        String zip = results[0].addressComponents[len - 1].longName;
        System.out.println("ZIP : " + zip);

        this.lat = latLng.lat;
        this.lon = latLng.lng;
        if (zip != null && !zip.equals("")) {
          this.zip = zip;
        }

      }

    }
    catch (Exception e) {
      e.printStackTrace();
    }

  }

  /**
   * 正ジオコーダー応答取得
   *
   * @param address
   * @return
   * @throws InterruptedException
   * @throws IOException
   */
  public GeocodingResult[] getResults(String address) throws InterruptedException, IOException {

    GeocodingApiRequest req = GeocodingApi.newRequest(context)
            .address(address)
            // .components(ComponentFilter.country("JP"))
            .language("ja");

    try {
      GeocodingResult[] results = req.await();
      if (results == null || results.length == 0) {
        // ZERO_RESULTSはresults.length==0の空配列がsuccessful扱いで返ってくる
        System.out.println("zero results.");
      }
      //results[0].geometry;

      return results;

    }
    catch (Exception e) {
      System.out.println("error.");
      System.out.println(e);
      return null;
    }
  }

  /**
   * 逆ジオコーダーの実行
   */
  public void execRVGeoCorder(String lat_lon) {
    
      try {
      GeocodingResult results[] = this.getRVResults(lat_lon);
      //LatLng latLng = results[0].geometry.location; // とりあえず一番上のデータを使う

      if (results != null && results.length > 0) {
        
        int len = results[0].addressComponents.length;
        String zip = results[0].addressComponents[len - 1].longName;
        System.out.println("ZIP : " + zip);
        this.addr = results[0].formattedAddress.replaceAll("日本、", "");
        System.out.println("ADDR : " + this.addr);
        if (zip != null && !zip.equals("")) {
          this.zip = zip;
        }

      }

    }
    catch (Exception e) {
      e.printStackTrace();
    }


  }

  public GeocodingResult[] getRVResults(String lat_lon) throws InterruptedException, IOException {
    
    

    GeocodingApiRequest req = GeocodingApi.newRequest(context)
            .address(lat_lon)
            // .components(ComponentFilter.country("JP"))
            .language("ja");

    try {
      GeocodingResult[] results = req.await();
      if (results == null || results.length == 0) {
        // ZERO_RESULTSはresults.length==0の空配列がsuccessful扱いで返ってくる
        System.out.println("zero results.");
      }
      //results[0].geometry;

      return results;

    }
    catch (Exception e) {
      System.out.println("error.");
      System.out.println(e);
      return null;
    }
  }

  public static void main(String[] args) {
    
    Geocorder G = new Geocorder(false);  
    G.execRVGeoCorder("31.62457 131.8521498");
    System.out.println(G.addr);
    


  }

}

ここで教えていただきました : http://developers.goalist.co.jp/entry/2017/10/16/150000

逆ジオコーダーも必要あるので、試したところ、Javascript の住所指定と同じく、緯度 + ” ” + 経度 を住所として渡せば出来ました。

Java selenium Implicit Wait を使う

selenium でJavaでスクレープする時、読込が終わるまでの待機方法がわからず、Thread.sleep(2000) とかをクリック後の処理に入れてましたが、即完了する場合、不要でタイムアウトして確実に実行できないことも多いので、ちょっと調べたところ、
Implicit Wait を使うと解決するのがわかり、いつも使ってます。

[ コード例 ]



import java.util.concurrent.TimeUnit;    // <== これが必要

    if (this.isHeadless) {
      ChromeOptions options = new ChromeOptions();
      options.addArguments("--headless", "--disable-gpu");
      driver = new ChromeDriver(options);
    }
    else {
      driver = new ChromeDriver();
    }

    // タイムアウトを10秒にセット //
    driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);

ここで教えて頂きました: http://softwaretest.jp/labo/tech/labo-294/

JAVA Runtime の外部プロセス実行で ” で囲んだ文字列が分かれて渡される (Linux)

少しはまったので備忘録しておきます。

[ コード例 ]



String spec = "Width : 23.50 Length : 234.20"
// 外部プログラムコマンド //
String cmd = this.phpGDCommandStamp + " " + imgfnm + " " + outfnm + " "
	      // Windows では通用した //
        //+ "\"" + spec + "\" " +  getPanelColorConfig(spec) + " 48 H " + colrgbMap.get(col) + " "
        // Linux では分かれてしまうので、置き換えて外部プログラム側で元に戻す //
        + spec.replaceAll(" ", "_") + " " + getPanelColorConfig(spec) + " 48 H " + colrgbMap.get(col) + " "
        + this.phpGDCommandPath;
System.out.println(cmd);

String restxt = "";
try {

  Runtime runtime = Runtime.getRuntime();
  Process p = runtime.exec(cmd);
  InputStream is = p.getInputStream();

  // レスポンス取得 //
  int nread;
  byte[] rbuf = new byte[200];
  while ((nread = is.read(rbuf)) > 0) {
  }
  restxt = new String(rbuf, "US-ASCII");

}
catch (Exception e) {
  e.printStackTrace();
}
System.out.println(restxt);

OpenJDK 9 の導入と probe3.2.0 の動作確認

JDK が Ver11 が最新になったようで、サーバーで OpenJDK 8 を使っているので、いずれ近いか遠いかわかりませんが、将来的にオラクルのサポート対象外になると思い、

OpenJDK9 のインストールと Tomcat9 + probe での動作をテストしてみました。

OpenJDK は、9.0.4
Tomcat は最新の 9.0.14
probe は最新の Version 3.2.0 (built at 2018-12-20T22:06:41Z)

を、VirtualBox の CentOS7 に導入したところ、
無事、ちゃんと動きました。


[root@localhost local]# java -version
openjdk version &quot;9.0.4&quot;
OpenJDK Runtime Environment (build 9.0.4+11)
OpenJDK 64-Bit Server VM (build 9.0.4+11, mixed mode)
[root@localhost local]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core) 

OpenJDK8 で ImageIO が原因未解決なエラーが多く、ImageMagick で代替したところ、脆弱性問題でよろしくないようで、PHP GD でのシェル利用による代替してましたが、

やはり、他に依存せず、JAVA で完結させるのがパフォーマンス上、管理上、ベストと思いますので、WEBアプリケーションでの動作チェックをしていくことにします。

OpenJDK9 は、まだ yum のパッケージがないので、下で教えて頂きました

https://weblabo.oscasierra.net/installing-openjdk9-on-centos7/

Java Servlet ファイルアップロードの基本

管理人のわかりにくいコードよりも、以下に説明されているのが、一番シンプルでわかりやすいかと思い、リンクを貼っておきます。

Qiitaのリンク : https://qiita.com/ohke/items/bec00a69d3f538aab06b

[ 説明・感想 ]

  1. アノテーションを使っているので、web.xmlの記述がなくすっきりしている (最近はこれが普通か)
  2. 送信用ページでのクライアントのファイル名が使われ、サーバー内の隠蔽されたパスに保存されてるが、実際は違う名前で、別に設定されたパスに保存されることが多いと思います。
  3. サーバー内である場合、再配備の時に消えてしまうので、永続的利用を前提とした場合、上 2) が現実的
  4. あくまで、説明用としてご紹介して下さってるサンプルに、けちつけるのは控えます。
  5. JSP側で、ファイルサイズの制限をJavascriptで設定するのが、大容量アップロードを未然回避する為、重要と思いますが、servlet3の maxFileSize で制御出来るので、二重防御、将来的Javascript仕様変更の影響を受けず無難。
  6. マルチファイルの場合は、request.getParts()で配列でとれるので、ループして保存出来ます。

MyBatis MySQL 開発環境でのプーリング接続で、Too many connections の解消

本番環境では普通に JNDI のプーリングが効いて、接続数過多のエラーはまずは発生しないですが、NetBeansの開発環境で JNDI が使えないので (管理人が知らないだけなのかも)、MyBatis の設定は、MyBatis のPOOL を使ってます。


    <environment id="?????">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${msdriver}"/>
        <property name="url" value="${msurl}"/>
        <property name="username" value="${msusername}"/>
        <property name="password" value="${mspassword}"/>
        
        <!-- maybe not affect --> 
        <property name="poolMaximumActiveConnections" value="1000" /> 
        <property name="poolMaximumIdleConnections" value="1000" />
        <!--<property name="maxActive" value="300" />-->
        <!--<property name="maxWait" value="5000" />-->
              
      </dataSource>
    </environment>

正常にプーリングが効くはずなんですが、よく MySQL の JDBC が Too many connections でエラーを起こします。


Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected establishment of connection,  message from server: "Too many connections"
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
	at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
	at com.mysql.jdbc.Util.getInstance(Util.java:386)
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1014)
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:988)
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:974)
	at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1110)
	at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2465)
	at com.mysql.jdbc.ConnectionImpl.connectWithRetries(ConnectionImpl.java:2306)
	... 55 more

MySQL の my.ini (Windowsなので) の最大 300 にしていたのを、試しに 1000 に変更して、エラーの発生する処理 (連続1200レコードほどの INSERT) を行ったところ、

何故か、エラーなしに正常に処理が完了出来ました。

# The maximum amount of concurrent sessions the MySQL server will
# allow. One of these connections will be reserved for a user with
# SUPER privileges to allow the administrator to login even if the
# connection limit has been reached.
max_connections=1000

処理中、プロセス数を確認してみると、500位まで接続数が上昇し続け、アイドルなのが捨てられ、1桁に減りました。
MyBatis の問題か、connecter/J の問題かはわかりませんが、長年の??が解消しました。

groovy を試してみる

groovy を初めてさわってみて、ループ処理を試してみました。



package vesselsch.grtst

/**
 * groovy テストクラス
 * @author MyName
 */
public class testclass {
  
  private final def name='MyName'
  
  
  // デフォルトコンストラクタ //
  public testclass () {  
  }

  public def hello() {
    println "Hello $name!"
  }
  
  // 九九表 in loop //
  public def kuku_in () {
    
    println ('-- kuku_in --')
    
    // 99 //
    for (i in 1..9) {
      for (j in 1..9) {
        println "$i X $j = ${i * j}"
      }
    }
    
  }
  
  // 九九表 each loop //
  public def kuku_each () {
    
    println ('-- kuku_each --')
    
    def l
    (1..9).each {
      l = it
      (1..9).each {
        println "$l X $it = ${l * it}"
      }
    }
    
  }
  
  // 九九表 array loop //
  public def kuku_array () {
    
    println ('-- kuku_array --')
    
    def iary = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    def jary = [1, 2, 3, 4, 5, 6, 7, 8, 9]

    def ipos = 0
    def jpos = 0
    def itmp
    def jtmp
    iary.each {
      //println iary[pos++]
      itmp = iary[ipos]
      jpos = 0
      jary.each {
        //println "$iary[ipos] X $jary[jpos] = ${iary[ipos] * jary[jpos++]}"   // <== これはばつ
        // [1, 2, 3, 4, 5, 6, 7, 8, 9][ipos] X [1, 2, 3, 4, 5, 6, 7, 8, 9][jpos] = 5
        jtmp = jary[jpos]
        println "$itmp X $jtmp = ${iary[ipos] * jary[jpos]}"
        //println "${iary[ipos]} X ${jary[jpos]} = ${iary[ipos] * jary[jpos]}"
        jpos++
      }
      ipos++
    }
    
  }
  
  // 型推論テスト うるう年判定 //
  public def is_leap_year(y) {
    
    def isleap = false;     // デフォルト //

    if (y % 100 == 0) {
      if (y % 400 == 0) {
        isleap = true;
      }
    }
    else if (y % 4 == 0) {
      isleap = true;
    }
    return isleap
    
  }
  
  // 文字列反転 //
  public def reverseTxt (src) {
    
    def res = ""
    def len = src.length() - 1
    
    //println "$len"
    
    def tmp
    len.downto(0) {
      //tmp = src.getAt(it, ${it + 1})
      //println "$tmp"
      //println "$it"
      //tmp = src.getAt(it..(it + 1))
      //tmp = src.getAt($it..($it + 1))
      //tmp = src.getAt(it)
      //println "$tmp"
      
      res += src.getAt(it)
    }
    return res
    
    
  }
  
  public static void main(String[] args) {
    
    // インスタンス化 //
    def tst = new testclass ();
   
    
    // voidメソッド呼出し //
    tst.hello()
    tst.kuku_in()
    tst.kuku_each()
    tst.kuku_array()
    
    def isLeap = tst.is_leap_year(2018)
    
    println "$isLeap"
    
    
    // 関数呼び出しテスト //
    // その1:変数化 //
    def rev = tst.reverseTxt ("あいうえお")
    println "$rev"   
    // その2:直接化 //
    println "${tst.reverseTxt ('あいうえお')}"
    
    //def testclass tst2 = new testclass();   // <=== これは間違い
       
  }
  
  
}	

Tomcat9 JNDIデータソースの設定

プーリングコネクションするJNDIの設定を、server.xml に記述します。

[ 設定例 MySQL ]


      <Resource 
      driverClassName="com.mysql.jdbc.Driver" 
      maxActive="300" 
      maxIdle="100" 
      maxWait="5000" 
      name="jdbc/DATABASE_NAME" 
      password="??????" 
      type="javax.sql.DataSource" 
      url="jdbc:mysql://localhost:3306/DATABASE_NAME?characterEncoding=utf8&amp;autoReconnect=true&amp;useSSL=false" 
      username="??????" 
      
      validationQuery="select 1"
      testOnBorrow="true"
      testWhileIdle="true"
      timeBetweenEvictionRunsMillis="60000"
      />

5.7系にバージョンアップした時、はまったのですが、非リモートサーバーのデータベースの場合、urlに useSSL=false を入れる必要あります。(ないと劇遅)
Oracleの中の人に教えてもらったのですが、同一ホスト内の場合、127.0.0.1 より localhost が速いそうです。

[ 設定例 Firebird ]


    <Resource
      name="jdbc/DATABASE_NAME"
      type="javax.sql.DataSource"
      driverClassName="org.firebirdsql.jdbc.FBDriver"
      password="????????"
      maxIdle="40"
      maxWait="5000"
      username="????????"
      url="jdbc:firebirdsql:localhost/3050:DATABASE_NAME?lc_ctype=SJIS_0208"
      maxActive="100"
      
    />

[ 設定例 Oracle ]


    <Resource
      name="jdbc/SID_NAME"
      type="javax.sql.DataSource"
      driverClassName="oracle.jdbc.driver.OracleDriver"
      password="SID_NAME"
      maxIdle="40"
      maxWait="5000"
      username="????????"
      url="jdbc:oracle:thin:@???.???.???.???:1521:SID_NAME"
      maxActive="100"/>

[ 設定例 PostgreSQL ]


    <Resource
      name="jdbc/DBNAME"
      type="javax.sql.DataSource"
      driverClassName="org.postgresql.Driver"
      password="???????"
      maxIdle="40"
      maxWait="5000"
      username="??????"
      url="jdbc:postgresql://127.0.0.1/DBNAME"
      maxActive="100"/>

[ 設定例 SQLServer ]


     <Resource
      name="jdbc/DBNAME"
      type="javax.sql.DataSource"
      driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
      password="??????"
      maxIdle="40"
      maxWait="5000"
      username="??????"
      url="jdbc:sqlserver://???.????.???.???:1433;DatabaseName=DBNAME"
      maxActive="100" />