きり丸の技術日記

技術検証したり、資格等をここに残していきます。

ゾンビプロセスを探して殺す

小ネタ。プロセスを殺し切れずに残ってしまうことがよくあるので、コマンドを残します。

このコマンドは、ゾンビ化した開発サーバのプロセスがポートを握ったままになり、新しく開発サーバを起動できないときに使用します。

今回の記事では、ゾンビ化した開発サーバのプロセスを殺し、新しく開発サーバを起動できるような状態にします。

環境

  • Mac, Linux

Windows以外では実施できると思います。今回はGitHub Codespecesで検証してました。

コマンド

lsof -i -P | grep "LISTEN"
kill -9 371

解説

lsof

まずは、プロセス番号を確認するためにlsofを実行します。オプションの-i-Pで、起動中のプロセス番号を確認できます。

ポート公開しているプロセスを明示的に殺したいので、grep "LISTEN"で起動しているポートを探します。

codespace ➜ /workspaces/shell-practice (main) $ lsof -i -P | grep "LISTEN"
  
node     371 codespace   18u  IPv6  41533      0t0  TCP *:35099 (LISTEN)

kill

lsofで対象のプロセス番号(PID)を検索できたので、killコマンドでプロセスを殺します。オプションの-9を付けることで、強制的に殺すことができます。

kill -9 371

※ GitHub Codespacesではリモート操作できるようにデフォルトでnodeが起動しています。こちらを実行してしまうと、GitHub Codespacesにしばらくアクセスできなくなるので、注意してください。

終わりに

LPICを覚えている人は簡単に覚えてるんでしょうね。

私は毎回調べる必要があったので、ブログにまとめました。まとめたことによって知識が蓄積して、このブログを使わなくなるかもしれませんが…。

まぁ、半年後にはまた忘れてそうなので、その時の自分に向けた記事にします。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考

Springで同一クラス別コンポーネントをDIしたい(nameGeneratorとFullyQualifiedAnnotationBeanNameGenerator)

Springで開発していると、別のコンポーネントだが、同一のクラス名を作りたくなることがあります。この同名クラスをDIしない場合は、特に問題は発生しません。しかし、同一クラス名をDIしてしまうと、BeanDifinitionStoreExceptionが発生してしまいます。

これは、クラスをパッケージプライベートにして可視性を狭めても、発生してしまいます。

クラス名を変更する、Beanの名前を変更する等々で簡単に回避することはできますが、クラス名が長くなったり、DIする際に変更した名前を指定する必要もあり、非常に面倒です。

それを簡単に解決できるのが、nameGeneratorFullyQualifiedAnnotationBeanNameGeneratorです。

当記事では、nameGeneratorを使用して別パッケージに配備した同一クラスの別コンポーネントを簡単にDIすることを目標とします。

環境

  • Java
    • 15
  • SpringBoot
    • 2.4.5

ゴール

  • 別パッケージに同一のクラス名を定義しても、エラーが発生しない

対応しない場合は次のエラーが発生するので、それを回避します。

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [kirimaru.DemoApplication]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'conflictService' for bean class [kirimaru.biz.domain.conflict.update.ConflictService] conflicts with existing, non-compatible bean definition of same name and class [kirimaru.biz.domain.conflict.register.ConflictService]

...中略...

Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'conflictService' for bean class [kirimaru.biz.domain.conflict.update.ConflictService] conflicts with existing, non-compatible bean definition of same name and class [kirimaru.biz.domain.conflict.register.ConflictService]

要約

  • デフォルトのBean名を変更する
    • ComponentScanのnameGeneratorにFullyQualifiedAnnotationBeanNameGeneratorを指定する

パッケージ構成

registerとupdateパッケージの下に、Bean名が被るように同一クラス名のConflictService.javaが存在させます。

- Appllication.java
  - registerパッケージ
    - ConflictService.java
  - updateパッケージ
    - ConflictService.java

ConflictServiceの中身

DIができるように、Serviceアノテーションを付けただけのファイルを作成します。DI対象に含められれば良いので、Controller, Componentでも問題ありません。

@Service
public class ConflictService {
}

対応

SpringBootApplicationを指定しているクラスに、ComponentScanを指定します。

次に、ComponentScanのnameGeneratorにFullyQualifiedAnnotationBeanNameGenerator.classを指定します。

@SpringBootApplication
@ComponentScan(nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class)
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

説明

デフォルトのBean名はクラス名になります。今回の場合、ConflictServiceです。

今回のFullyQualifiedAnnotationBeanNameGeneratorを指定することで、生成されるBean名が完全修飾クラス名になります。今回の場合、register.ConflictServiceupdate.ConflictServiceの2つに分かれます。

Javaでは同一パッケージ、同一クラスは配備できないので、どのパターンでも対応できると思います。

なお、FullyQualifiedAnnotationBeanNameGeneratorはSpringFrameworkの5.2.3に導入されました。このリリースは2020年1月14日と最近のリリースです。もしSpringのアップデートできないが、同様の処理をしたいという場合にはこちらの方の記事を参考にしてください。

備考

もし、SpringBootApplicationにscanBasePackagesを指定している場合は、ComponentScanのbasePackagesにも同様に指定してください。

こちらを指定しないと、探索するパッケージが異なるために目的通りのDIができないことがあります。

@SpringBootApplication(scanBasePackages = {"kirimaru"})
@ComponentScan(basePackages = "kirimaru", nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class)
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

テストする方法

※ ここからは一般論ではなく、私のローカル環境で発生したものです。職場で導入したら別のエラーで動きませんでした…。


FullyQualifiedAnnotationBeanNameGeneratorを使用し、Bean名が変更されたので、DIできるように設定する必要があります。

私の場合は、こちらのアノテーションを追加すると、読み込むことができるようになりました。

  • @AutoConfigureWebClient

WebMvcTestを使用していれば、次のアノテーションを含んでいるので意識して追加する必要は少ないと思います。

  • @AutoConfigureCache
  • @AutoConfigureWebMvc
  • @AutoConfigureMockMvc

意識的に読み込まないといけないので、読み込むアノテーションの数が増えたら、新しいアノテーションを作ってもいいかもしれません。


SpringJUnitConfigを使用している場合は、ComponentScanを併用していると思いますので、こちらにnameGeneratorを追加すると動くようになると思われます。

@ComponentScan(value = {"kirimaru.biz.domain"}, nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class)
static class Config {
}

ソースコード

実装コード https://github.com/hirotoKirimaru/cucumber-sample/blob/36d3cec61c97321dadb744ce70a15b5f35e67730/src/main/java/kirimaru/DemoApplication.java

終わりに

テストができなくて記事にできませんでしたが、teratailで回答いただけでようやくテストができるようになりました。WebMvcTestのアノテーションを使っていたので、十分インポートできていると思っていたのですが、まだ足りなかったとは…。

この辺の設定周りは苦手ですね。精進していきたいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

経緯

FullyQualifiedAnnotationBeanNameGeneratorを知った経緯。いろふさんありがとうございました。

twitter.com

twitter.com

参考

【Java・Spring Boot】別パッケージにて、同じクラス名でDIしたいとき 【Java・Spring Boot】別パッケージにて、同じクラス名でDIしたいとき | 地方に引越し予定の30歳男がプログラマーを続けるためのブログ

teratail: デフォルト以外のBean名生成をしつつ、WebMvcTest等でDIを行うテストをしたい Spring - デフォルト以外のBean名生成をしつつ、WebMvcTest等でDIを行うテストをしたい|teratail

Spring JavaDoc: FullyQualifiedAnnotationBeanNameGenerator FullyQualifiedAnnotationBeanNameGenerator (Spring Framework 5.3.14 API)

ImportAutoConfigurationのパッケージ クラス org.springframework.boot.autoconfigure.ImportAutoConfiguration の使用 (Spring Boot 2.6.1 API) - Javadoc

自動構成アノテーションのテスト Spring Boot テスト自動構成アノテーション - リファレンス

f:id:nainaistar:20210504221725p:plain

Rubyのtapとthenメソッドを理解するために素振りした

Rubyのメソッドよくわからないものが多いので、素振りします。

今回の記事では、tapメソッドとthenメソッドを理解するために素振りしました。なお、私はJavaをメインとしたエンジニアのため、Javaに置き換えた記載もあります。ご了承ください。

環境

  • Ruby
    • 3.0.2p107
  • RSpec

要約

tapメソッド

returnはself。元のインスタンスに対して副作用を起こします。

Javaに置き換えるとラムダ式のpeekメソッドに似ている。

thenメソッド

returnはBlock。元のインスタンスに対して副作用を起こします。yield_selfメソッドと同じ(エイリアス)。

Javaに置き換えるとラムダ式のmapメソッドに似ている。

ユースケース

  • setterの処理をひとまとめにしたい
  • HTTP通信のRequest/Response Bodyをログに残すために、中間操作を加える

挙動確認

自作クラスでないと挙動確認が難しかったため、開始日と終了日を持ったクラスを用意します。

class Term
  attr_accessor :start_date, :end_date
end

tapメソッド

selfを渡して、selfを返却します。Returnはselfになるため、selfに値をsetしないと変更された状態で返却されません。

Javaだとラムダ式のpeekに似ています。

it 'selfに値をセットしていないので1yearsされていない' do
    time = Time.new
    actual = time.tap { |t| t + 1.years }
    # => 返却されるのはTimeクラス
    expect(actual.year).to eq time.year
end

it 'selfに値をセットしているので1yearsされている' do
    term = Term.new
    time = Time.now

    actual = term.tap{ |t| t.start_date = t.start_date + 1.years }
    # => 返却されるのはTermクラス

    # 副作用も起こす
    expect(term.start_date.year).to eq time.year + 1
    expect(term.end_date.year).to eq time.year

    # 返却もSelf
    expect(actual.start_date.year).to eq time.year + 1
    expect(actual.end_date.year).to eq time.year
end

値をsetする箇所を1つにまとめられるため、複数の値をセットしている場合に処理が散らずに可読性が上がりそうです。

# 特にtapを使わない場合
term.start_date += 1.years

# tapを使っている場合
term.tap do |t|
   t.start_date += 1.years
end

# tapを使って、変更をメソッドにまとめている場合
term.tap(&:start_date_update!)

class Term
  attr_accessor :start_date, :end_date

  def start_date_update!(add_year: 1.years)
    @start_date += add_year
  end
end

thenメソッド

selfを渡して、ブロックの結果を返却します。yield_selfメソッドと同じ挙動をします(エイリアス)。tapbreakを組み合わせても、thenメソッドと同じような挙動を得られるそうです。

Javaだと副作用の有無はありますが、ラムダ式のmapメソッドに似ています。

it '値をセットしていないがthenのブロック結果を返却するので1yearsされている' do
    time = Time.new
    actual = time.then { |t| t + 1.years }
    # => t+1.yearsしたTime型が返却される
    expect(actual.year).to eq time.year + 1
end

it '値をセットしていないがthenのブロック結果を返却するので1yearsされている' do
    term = Term.new
    time = Time.now

    actual = term.then do |t|
        t.start_date = t.start_date + 1.years
    end
    # => ブロックで最後にセットしているTime型が返却される

    # 副作用も起こす
    expect(term.start_date.year).to eq time.year + 1
    expect(term.end_date.year).to eq time.year
    # 返却はブロックの結果(値のセットが最後なので、そうなる)
    expect(actual.year).to eq time.year + 1
end

メソッドチェインを使いつつ、副作用を起こしうることを表現できそうです。

it 'HelloWorldを大文字にして、反転させる' do
    actual = "Hello, world!".then(&:upcase).then(&:reverse)
    expect(actual).to eq "!DLROW ,OLLEH"
end

it 'HelloWorldを大文字にして、反転させる(thenなし)' do
    actual = "Hello, world!".upcase.reverse
    expect(actual).to eq "!DLROW ,OLLEH"
end

tapとthenメソッドを組み合わせる

そのままだとミュータブルになってしまうので、イミュータブルな操作をするために一回clone等のコピーメソッドを仲介すると良いかもしれません。

個人的にはイミュータブルな操作を目指したいのですが、Rubyでイミュータブルにするメリットは分からないので、あまり一般的な使われ方ではないかもしれません。

# ※ cloneはシャローコピーなので、ディープコピーする際は自作メソッドを作ってください
it 'thenでcloneしてtapで値を変更する' do
    term = Term.new
    time = Time.now

    actual = term
                .then(&:clone)
                .tap { |t| t.start_date = t.start_date + 1.years }
    # cloneするからイミュータブルにする
    expect(term.start_date.year).to eq time.year
    expect(term.end_date.year).to eq time.year

    # cloneした結果をtapで返却する
    expect(actual.start_date.year).to eq time.year + 1
    expect(actual.end_date.year).to eq time.year
end

ソースコード

終わりに

個人的には、複数の値を1つのブロック内でsetできて、可読性が上がりそうな点が良いと思いました。

ただ、今のところそれ以外の操作が思いつきません。

うまく使いこなせれば可読性が上がるメソッドだと思うのですが、可読性以外のメリットがわからないのでもう少し使ってから考えたいと思います。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考情報


教えていただいた情報:

f:id:nainaistar:20211212185142p:plain